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

누구든지 이것을 할 수있는 좋은 방법을 볼 수 있습니까?

답변:


298

C++11 개의 람다 사용 :

#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
컴파일러가 람다를 지원하지 않는 경우 클래스를 사용할 수 있습니다. template <typename T> class CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public : CompareIndicesByAnotherVectorValues ​​(std :: vector <T> * values) : _values ​​(values) {} public : bool operator () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ 비]; }};
Yoav

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>IOTA에 대한 ()
kartikag01

6
iota전체 C ++ 표준 라이브러리에서 가장 명백하게 명명 된 알고리즘입니다.
세스 존슨

87

int 대신 std :: pair를 정렬 할 수 있습니다. 첫 번째 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

1
이것은 분명히 실행 가능한 접근 방법이지만 원래 컨테이너를 "숫자 컨테이너"에서 "쌍 컨테이너"로 변경해야한다는 단점이 있습니다.
Ruslan

19

주어진 벡터가

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()보다 elegent 초기화에map
니므롯 모라 그

예, 우리는 그것을 사용할 수 있습니다! 제안 주셔서 감사합니다
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)) ;

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! 이 템플릿 함수를 어떻게 인스턴스화합니까? 두 개의 템플릿 유형 이름이 있으며 그 중 하나는이 상황을 매우 드물게 만드는 반복자입니다. 도와 줄래?
Scott Yang

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 이전에 붙어 있고 람다를 사용할 수 없습니다. 감사합니다 Aditya Aswal.
stephanmg

6

나는이 질문에 부딪 쳤고, 반복자를 직접 정렬하는 것이 값을 정렬하고 인덱스를 추적하는 방법이라고 생각했다. pair값이 큰 객체 일 때 도움이되는 추가 컨테이너 (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( )합니다.


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
}

그 이유는 선호 std::multimapA와는 std::map원래 벡터의 동일한 값을 허용한다. 또한는 다르게, 참고하시기 바랍니다 std::map, operator[]정의되지 않은std::multimap .


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

이데온


1

벡터의 항목이 고유합니까? 그렇다면 벡터를 복사하고 복사본 중 하나를 STL 정렬로 정렬하십시오. 로 한 다음 각 항목이 원래 벡터에있는 인덱스를 찾을 수 있습니다.

벡터가 중복 항목을 처리해야한다면 자체 정렬 루틴을 구현하는 것이 좋습니다.


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

이 유형의 질문의 경우 orignal 배열 데이터를 새 데이터에 저장 한 다음 정렬 된 배열의 첫 번째 요소를 복제 된 배열로 이진 검색하고 해당 색인을 벡터 나 배열에 저장해야합니다.

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

이진 검색은 배열, 배열의 크기, 검색 항목을 가져 와서 검색 된 항목의 위치를 ​​반환하는 함수입니다


-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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.