C ++排序和跟踪索引

问题描述 投票:189回答:15

使用C ++,希望是标准库,我想按升序对一系列样本进行排序,但我还想记住新样本的原始索引。

例如,我有一个集合,矢量或样本矩阵A : [5, 2, 1, 4, 3]。我想将它们排序为B : [1,2,3,4,5],但我也想记住值的原始索引,所以我可以得到另一个集合:C : [2, 1, 4, 3, 0 ] - 它对应于'B'中每个元素的索引,在原来的'A'。

例如,在Matlab中你可以这样做:

 [a,b]=sort([5, 8, 7])
 a = 5 7 8
 b = 1 3 2

任何人都可以看到这样做的好方法吗?

c++ sorting stl indexing
15个回答
263
投票

使用C ++ 11 lambda

#include <iostream>
#include <vector>
#include <numeric>      // std::iota
#include <algorithm>    // std::sort

template <typename T>
vector<size_t> sort_indexes(const vector<T> &v) {

  // initialize original index locations
  vector<size_t> idx(v.size());
  iota(idx.begin(), idx.end(), 0);

  // sort indexes based on comparing values in v
  sort(idx.begin(), idx.end(),
       [&v](size_t i1, size_t i2) {return v[i1] < v[i2];});

  return idx;
}

现在,您可以在迭代中使用返回的索引向量,例如

for (auto i: sort_indexes(v)) {
  cout << v[i] << endl;
}

显然,您还可以选择提供自己的原始索引向量,排序函数,比较器,或使用额外的向量在sort_indexes函数中自动重新排序v。


1
投票

还有另一种方法可以解决这个问题,使用地图:

vector<double> v = {...}; // input data
map<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m[*it] = it - v.begin();

这将消除非独特的元素。如果这不可接受,请使用multimap:

vector<double> v = {...}; // input data
multimap<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m.insert(make_pair(*it, it - v.begin()));

为了输出索引,迭代map或multimap:

for (auto it = m.begin(); it != m.end(); ++it)
    cout << it->second << endl;

1
投票

好吧,我的解决方案使用残留技术。我们可以将值放在排序的上面2个字节和元素的索引中 - 在较低的2个字节中:

int myints[] = {32,71,12,45,26,80,53,33};

for (int i = 0; i < 8; i++)
   myints[i] = myints[i]*(1 << 16) + i;

然后像往常一样对数组myints进行排序:

std::vector<int> myvector(myints, myints+8);
sort(myvector.begin(), myvector.begin()+8, std::less<int>());

之后,您可以通过残差访问元素的索引。以下代码打印按升序排序的值的索引:

for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it)
   std::cout << ' ' << (*it)%(1 << 16);

当然,这种技术仅适用于原始数组myints中的相对较小的值(即那些可以适合int的高2字节的值)。但它具有区分myints相同值的额外好处:它们的指数将以正确的顺序打印。


1
投票

对于这种类型的问题,将原始数组数据存储到新数据中,然后将已排序数组的第一个元素二进制搜索到重复数组中,并将该indice存储到向量或数组中。

input array=>a
duplicate array=>b
vector=>c(Stores the indices(position) of the orignal array
Syntax:
for(i=0;i<n;i++)
c.push_back(binarysearch(b,n,a[i]));`

这里binarysearch是一个函数,它接受数组,数组大小,搜索项,并返回搜索项的位置


1
投票

如果可能,您可以使用find函数构建位置数组,然后对数组进行排序。

或者也许您可以使用键作为元素的映射,以及值在即将到来的数组中的位置列表(A,B和C)

这取决于后来使用这些数组。


1
投票

考虑使用@Ulrich Eckhardt建议的std::multimap。只是代码可以变得更简单。

特定

std::vector<int> a = {5, 2, 1, 4, 3};  // a: 5 2 1 4 3

以平均插入时间排序

std::multimap<int, std::size_t> mm;
for (std::size_t i = 0; i != a.size(); ++i)
    mm.insert({a[i], i});

检索值和原始索引

std::vector<int> b;
std::vector<std::size_t> c;
for (const auto & kv : mm) {
    b.push_back(kv.first);             // b: 1 2 3 4 5
    c.push_back(kv.second);            // c: 2 1 4 3 0
}

选择std::multimapstd::map的原因是允许原始向量中的相等值。另请注意,与std::map不同,operator[]没有为std::multimap定义。


0
投票

有很多方法。一个相当简单的解决方案是使用2D矢量。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
 vector<vector<double>> val_and_id;
 val_and_id.resize(5);
 for (int i = 0; i < 5; i++) {
   val_and_id[i].resize(2); // one to store value, the other for index.
 }
 // Store value in dimension 1, and index in the other:
 // say values are 5,4,7,1,3.
 val_and_id[0][0] = 5.0;
 val_and_id[1][0] = 4.0;
 val_and_id[2][0] = 7.0;
 val_and_id[3][0] = 1.0;
 val_and_id[4][0] = 3.0;

 val_and_id[0][1] = 0.0;
 val_and_id[1][1] = 1.0;
 val_and_id[2][1] = 2.0;
 val_and_id[3][1] = 3.0;
 val_and_id[4][1] = 4.0;

 sort(val_and_id.begin(), val_and_id.end());
 // display them:
 cout << "Index \t" << "Value \n";
 for (int i = 0; i < 5; i++) {
  cout << val_and_id[i][1] << "\t" << val_and_id[i][0] << "\n";
 }
 return 0;
}

这是输出:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7

85
投票

您可以对std :: pair进行排序,而不仅仅是整数 - 第一个int是原始数据,第二个int是原始索引。然后提供一个只对第一个int进行排序的比较器。例:

Your problem instance: v = [5 7 8]
New problem instance: v_prime = [<5,0>, <8,1>, <7,2>]

使用比较器对新问题实例进行排序:

typedef std::pair<int,int> mypair;
bool comparator ( const mypair& l, const mypair& r)
   { return l.first < r.first; }
// forgetting the syntax here but intent is clear enough

使用该比较器的st_ :: sort on v_prime的结果应该是:

v_prime = [<5,0>, <7,2>, <8,1>]

你可以通过遍历向量来剥离索引,从每个std :: pair中获取.second。


12
投票

我写了索引排序的通用版本。

template <class RAIter, class Compare>
void argsort(RAIter iterBegin, RAIter iterEnd, Compare comp, 
    std::vector<size_t>& indexes) {

    std::vector< std::pair<size_t,RAIter> > pv ;
    pv.reserve(iterEnd - iterBegin) ;

    RAIter iter ;
    size_t k ;
    for (iter = iterBegin, k = 0 ; iter != iterEnd ; iter++, k++) {
        pv.push_back( std::pair<int,RAIter>(k,iter) ) ;
    }

    std::sort(pv.begin(), pv.end(), 
        [&comp](const std::pair<size_t,RAIter>& a, const std::pair<size_t,RAIter>& b) -> bool 
        { return comp(*a.second, *b.second) ; }) ;

    indexes.resize(pv.size()) ;
    std::transform(pv.begin(), pv.end(), indexes.begin(), 
        [](const std::pair<size_t,RAIter>& a) -> size_t { return a.first ; }) ;
}

除了用于接收已排序索引的索引容器之外,用法与std :: sort的用法相同。测试:

int a[] = { 3, 1, 0, 4 } ;
std::vector<size_t> indexes ;
argsort(a, a + sizeof(a) / sizeof(a[0]), std::less<int>(), indexes) ;
for (size_t i : indexes) printf("%d\n", int(i)) ;

对于没有c ++ 0x支持的编译器,你应该得到2 1 0 3.将lamba表达式替换为类模板:

template <class RAIter, class Compare> 
class PairComp {
public:
  Compare comp ;
  PairComp(Compare comp_) : comp(comp_) {}
  bool operator() (const std::pair<size_t,RAIter>& a, 
    const std::pair<size_t,RAIter>& b) const { return comp(*a.second, *b.second) ; }        
} ;

并重写std :: sort as

std::sort(pv.begin(), pv.end(), PairComp(comp)()) ;

11
投票
vector<pair<int,int> >a;

for (i = 0 ;i < n ; i++) {
    // filling the original array
    cin >> k;
    a.push_back (make_pair (k,i)); // k = value, i = original index
}

sort (a.begin(),a.end());

for (i = 0 ; i < n ; i++){
    cout << a[i].first << " " << a[i].second << "\n";
}

现在a包含我们的值和它们在排序中的相应索引。

a[i].first = value'th的i

a[i].second = idx在初始阵列中。


7
投票

它似乎比它看起来容易。

假设给定的向量是

A=[2,4,3]

创建一个新的向量

V=[0,1,2] // indicating positions

排序V并在排序时而不是比较V的元素,比较A的相应元素

 //Assume A is a given vector with N elements
 vector<int> V(N);
 int x=0;
 std::iota(V.begin(),V.end(),x++); //Initializing
 sort( V.begin(),V.end(), [&](int i,int j){return A[i]<A[j];} );

6
投票

我遇到了这个问题,并想出直接对迭代器进行排序将是一种对值进行排序并跟踪索引的方法;没有必要定义一个额外的pairs(value,index)容器,当值是大对象时这是有用的;迭代器提供对值和索引的访问:

/*
 * a function object that allows to compare
 * the iterators by the value they point to
 */
template < class RAIter, class Compare >
class IterSortComp
{
    public:
        IterSortComp ( Compare comp ): m_comp ( comp ) { }
        inline bool operator( ) ( const RAIter & i, const RAIter & j ) const
        {
            return m_comp ( * i, * j );
        }
    private:
        const Compare m_comp;
};

template <class INIter, class RAIter, class Compare>
void itersort ( INIter first, INIter last, std::vector < RAIter > & idx, Compare comp )
{ 
    idx.resize ( std::distance ( first, last ) );
    for ( typename std::vector < RAIter >::iterator j = idx.begin( ); first != last; ++ j, ++ first )
        * j = first;

    std::sort ( idx.begin( ), idx.end( ), IterSortComp< RAIter, Compare > ( comp ) );
}

至于用法示例:

std::vector < int > A ( n );

// populate A with some random values
std::generate ( A.begin( ), A.end( ), rand );

std::vector < std::vector < int >::const_iterator > idx;
itersort ( A.begin( ), A.end( ), idx, std::less < int > ( ) );

现在,例如,排序向量中的第五个最小元素将具有值**idx[ 5 ],并且其在原始向量中的索引将是distance( A.begin( ), *idx[ 5 ] )或简单地为*idx[ 5 ] - A.begin( )


2
投票

在函数中创建std::pair然后排序对:

通用版本:

template< class RandomAccessIterator,class Compare >
auto sort2(RandomAccessIterator begin,RandomAccessIterator end,Compare cmp) ->
   std::vector<std::pair<std::uint32_t,RandomAccessIterator>>
{
    using valueType=typename std::iterator_traits<RandomAccessIterator>::value_type;
    using Pair=std::pair<std::uint32_t,RandomAccessIterator>;

    std::vector<Pair> index_pair;
    index_pair.reserve(std::distance(begin,end));

    for(uint32_t idx=0;begin!=end;++begin,++idx){
        index_pair.push_back(Pair(idx,begin));
    }

    std::sort( index_pair.begin(),index_pair.end(),[&](const Pair& lhs,const Pair& rhs){
          return cmp(*lhs.second,*rhs.second);
    });

    return index_pair;
}

ideone


2
投票

@Lukasz Wiklendt的美丽解决方案!虽然在我的情况下我需要更通用的东西,所以我修改了一下:

template <class RAIter, class Compare>
vector<size_t> argSort(RAIter first, RAIter last, Compare comp) {

  vector<size_t> idx(last-first);
  iota(idx.begin(), idx.end(), 0);

  auto idxComp = [&first,comp](size_t i1, size_t i2) {
      return comp(first[i1], first[i2]);
  };

  sort(idx.begin(), idx.end(), idxComp);

  return idx;
}

示例:查找按长度排序字符串向量的索引,除了第一个虚拟元素。

vector<string> test = {"dummy", "a", "abc", "ab"};

auto comp = [](const string &a, const string& b) {
    return a.length() > b.length();
};

const auto& beginIt = test.begin() + 1;
vector<size_t> ind = argSort(beginIt, test.end(), comp);

for(auto i : ind)
    cout << beginIt[i] << endl;

打印:

abc
ab
a

1
投票

矢量中的项目是否唯一?如果是这样,复制矢量,使用STL Sort对其中一个副本进行排序,然后您可以找到每个项目在原始矢量中具有的索引。

如果向量应该处理重复项,我认为你最好实现自己的排序例程。

© www.soinside.com 2019 - 2024. All rights reserved.