C ++排序和跟踪索引


216

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

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

例如,在Matlab中,您可以执行以下操作:

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

谁能看到一个很好的方法来做到这一点?

Answers:


298

使用C++11个lambda:

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

using namespace std;

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
  // using std::stable_sort instead of std::sort
  // to avoid unnecessary index re-orderings
  // when v contains elements of equal values 
  stable_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。


4
喜欢这个答案。如果您的编译器不支持lambda,则可以使用以下类:template <typename T> class CompareIndicesByAnotherVectorValues {std :: vector <T> * _values; 公用:CompareIndicesByAnotherVectorValues(std :: vector <T> *值):_values(values){}公用:bool运算符()(const int&a,const int&b)const {return(_values)[a]>( _values)[ b]; };
Yoav 2012年

2
我也很喜欢这个答案,无需复制原始向量即可创建对向量。
headmyshoulder

29
for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;我更喜欢手工,而不是手工制作std::iota( idx.begin(), idx.end(), 0 );
Wyck

6
使用#include <numeric>了丝毫()
kartikag01

6
iota是整个C ++标准库中命名最少的算法。
塞斯·约翰逊

87

您可以对std :: pair而不是int进行排序-第一个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

使用该比较器的v_prime上std :: sort的结果应为:

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

您可以通过遍历向量,从每个std :: pair抓住.second来剥离索引。


1
这也正是我要做的。基本排序功能不会跟踪新旧位置,因为这会增加不必要的开销。
the_mandrill

8
此功能的缺点是它要求您为所有值重新分配内存。
Yoav 2012年

1
这显然是一种可行的方法,但是它有一个缺点,那就是您必须将原始容器从“数字容器”更改为“对容器”。
罗斯兰

17

假设给定向量为

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];} );

爱你的答案。您甚至可以使用std::iota()更优雅的初始化方式map
Nimrod Morag

是的,我们可以使用它!感谢您的建议
MysticForce

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)) ;

您应该得到2 1 0 3.对于不支持c ++ 0x的编译器,将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为

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

嗨,hkyi!我们如何实例化此模板功能?它有两个模板类型名,其中一个是迭代器,这使得这种情况非常罕见。你能帮忙吗?
杨致远

12
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 = valuei

a[i].second = idx 在初始数组中。


考虑加入你的代码的描述,使得浏览这个帖子的用户可以了解如何它的工作原理。
BusyProgrammer

我实际上最喜欢此解决方案-我的向量大小为4左右,而且我被困在C ++ 11之前,不能使用lambda。感谢Aditya Aswal。
stephanmg

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 > ( ) );

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


3

还有一种解决方法,使用地图:

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();

但是,这将消除非唯一元素。如果不可接受,请使用多图:

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()));

为了输出索引,请遍历地图或多地图:

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

3

@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

3

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

给定

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
}

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


2

创建一个std::pairin函数,然后排序对:

通用版本:

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;
}

异丁酮


1

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

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


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(即那些可以放入的高2个字节的值int)。但是,这样做还有另一个好处:区分的相同值myints:它们的索引将以正确的顺序打印。


1

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

或者,也许您可​​以使用以键为元素的地图,并为其值在即将到来的数组(A,B和C)中的位置列表

这取决于这些数组的后续用途。


0

对于此类问题,将原始数组数据存储到新数据中,然后对排序后的数组的第一个元素进行二进制搜索到重复的数组中,然后将索引存储到向量或数组中。

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

有很多方法。一个相当简单的解决方案是使用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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.