Sắp xếp C ++ và theo dõi các chỉ mục


216

Sử dụng C ++ và hy vọng thư viện chuẩn, tôi muốn sắp xếp một chuỗi các mẫu theo thứ tự tăng dần, nhưng tôi cũng muốn nhớ các chỉ mục ban đầu của các mẫu mới.

Ví dụ, tôi có một tập hợp, hoặc vectơ hoặc ma trận mẫu A : [5, 2, 1, 4, 3]. Tôi muốn sắp xếp các giá trị này B : [1,2,3,4,5], nhưng tôi cũng muốn nhớ các chỉ mục gốc của các giá trị, vì vậy tôi có thể lấy một tập hợp khác: C : [2, 1, 4, 3, 0 ]- tương ứng với chỉ mục của từng thành phần trong 'B', trong bản gốc ' A '.

Ví dụ: trong Matlab bạn có thể làm:

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

Bất cứ ai cũng có thể thấy một cách tốt để làm điều này?

Câu trả lời:


298

Sử dụng C++11 lambdas:

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

Bây giờ bạn có thể sử dụng vectơ chỉ mục trả về trong các lần lặp như

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

Bạn cũng có thể chọn cung cấp vectơ chỉ mục gốc, hàm sắp xếp, bộ so sánh hoặc tự động sắp xếp lại v trong hàm sort_indexes bằng cách sử dụng một vectơ bổ sung.


4
Thích câu trả lời này. Nếu trình biên dịch của bạn không hỗ trợ lambdas, bạn có thể sử dụng một lớp: template <typename T> class So sánhInicesByAnotherVectorValues ​​{std :: vector <T> * _values; công cộng: CompareIndicesByAnotherVectorValues (std :: vector <T> * giá trị): _values (giá trị) {} public: nhà điều hành bool () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ b]; }};
Yoav

2
Tôi cũng thích câu trả lời này, không cần phải sao chép vectơ gốc để tạo ra vectơ của các cặp.
headmyshoulder

29
Thay vì làm thủ công, for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;tôi thích tiêu chuẩn hơnstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
sử dụng #include <numeric>cho iota ()
kartikag01

6
iotalà thuật toán được đặt tên rõ ràng nhất trong toàn bộ thư viện chuẩn C ++.
Seth Johnson

87

Bạn có thể sắp xếp std :: cặp thay vì chỉ ints - int đầu tiên là dữ liệu gốc, int thứ hai là chỉ mục gốc. Sau đó cung cấp một bộ so sánh chỉ sắp xếp trên int đầu tiên. Thí dụ:

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

Sắp xếp trường hợp vấn đề mới bằng cách sử dụng một bộ so sánh như:

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

Kết quả của std :: sort trên v_prime, sử dụng bộ so sánh đó, sẽ là:

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

Bạn có thể bóc tách các chỉ số bằng cách đi bộ vectơ, lấy .second từ mỗi cặp std ::.


1
Đây chính xác là cách tôi sẽ làm điều đó là tốt. Hàm sắp xếp cơ bản không theo dõi vị trí cũ so với vị trí mới vì điều đó sẽ bổ sung thêm chi phí không cần thiết.
the_mandrill

8
Hạn chế với chức năng này là nó yêu cầu bạn phải phân bổ lại bộ nhớ cho tất cả các giá trị.
Yoav

1
Đây rõ ràng là một cách tiếp cận khả thi, nhưng nó có một nhược điểm là bạn phải thay đổi bộ chứa ban đầu của mình từ "thùng chứa số" thành "thùng chứa các cặp".
Ruslan

18

Giả sử vector đã cho là

A=[2,4,3]

Tạo một vectơ mới

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

Sắp xếp V và trong khi sắp xếp thay vì so sánh các yếu tố của V, hãy so sánh các yếu tố tương ứng của 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];} );

Yêu câu trả lời của bạn. bạn thậm chí có thể sử dụng std::iota()để khởi tạo thanh lịch hơnmap
Nimrod Morag

Vâng, chúng tôi có thể sử dụng nó! Cảm ơn lời đề nghị
MysticForce

12

Tôi đã viết phiên bản chung của loại chỉ mục.

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

Cách sử dụng giống như của std :: sort ngoại trừ một bộ chứa chỉ mục để nhận các chỉ mục được sắp xếp. thử nghiệm:

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

bạn sẽ nhận được 2 1 0 3. đối với các trình biên dịch không có hỗ trợ c ++ 0x, thay thế biểu thức lamba như một mẫu lớp:

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

và viết lại std :: sort như

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

Chào hkyi! Làm thế nào để chúng tôi khởi tạo chức năng mẫu này? Nó có hai kiểu chữ mẫu và một trong số đó là một trình vòng lặp làm cho tình huống này rất hiếm. Bạn có thể giúp không?
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";
}

Hiện nay a chứa cả giá trị của chúng tôi và các chỉ số tương ứng của chúng trong sắp xếp.

a[i].first = value tại i 'th.

a[i].second = idx trong mảng ban đầu.


Xem xét thêm mô tả mã của bạn để người dùng truy cập bài đăng này có thể hiểu cách thức hoạt động của nó.
BusyProgrammer

Tôi thực sự thích giải pháp này nhất - vectơ của tôi có kích thước 4 hoặc hơn và tôi bị kẹt trước C ++ 11 và không thể sử dụng lambdas. Cảm ơn Aditya Aswal.
stephanmg

6

Tôi đã bắt gặp câu hỏi này và tìm ra cách sắp xếp các vòng lặp trực tiếp sẽ là một cách để sắp xếp các giá trị và theo dõi các chỉ số; Không cần xác định thêm một thùng chứa pairs (giá trị, chỉ mục) nào hữu ích khi các giá trị là các đối tượng lớn; Các trình vòng lặp cung cấp quyền truy cập vào cả giá trị và chỉ mục:

/*
 * 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 ) );
}

đối với ví dụ sử dụng:

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

bây giờ, ví dụ, phần tử nhỏ thứ 5 trong vectơ được sắp xếp sẽ có giá trị **idx[ 5 ]và chỉ mục của nó trong vectơ gốc sẽ distance( A.begin( ), *idx[ 5 ] )đơn giản hoặc đơn giản *idx[ 5 ] - A.begin( ).


3

Có một cách khác để giải quyết vấn đề này, sử dụng bản đồ:

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

Điều này sẽ xóa bỏ các yếu tố không độc đáo mặc dù. Nếu điều đó không được chấp nhận, hãy sử dụng đa chế độ:

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

Để xuất các chỉ số, lặp lại trên bản đồ hoặc multimap:

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

3

Giải pháp tuyệt vời của @Lukasz Wiklendt! Mặc dù trong trường hợp của tôi, tôi cần một cái gì đó chung chung hơn nên tôi đã sửa đổi nó một chút:

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

Ví dụ: Tìm các chỉ số sắp xếp một vectơ của chuỗi theo độ dài, ngoại trừ phần tử đầu tiên là hình nộm.

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;

in:

abc
ab
a

3

Cân nhắc sử dụng std::multimaptheo đề xuất của @Ulrich Eckhardt. Chỉ là mã có thể được thực hiện thậm chí đơn giản hơn.

Được

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

Để sắp xếp trong thời gian trung bình của chèn

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

Để lấy các giá trị và chỉ mục gốc

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
}

Lý do thích a hơn std::multimapa std::maplà cho phép các giá trị bằng nhau trong các vectơ gốc. Cũng xin lưu ý rằng, không giống như std::map, operator[]không được xác định cho std::multimap.


2

Làm một std::pair hàm sau đó sắp xếp cặp:

Phiên bản chung :

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

nhàn rỗi


1

Là các mục trong vector duy nhất? Nếu vậy, sao chép vectơ, sắp xếp một trong các bản sao với Sắp xếp STL sau đó bạn có thể tìm thấy chỉ mục mà mỗi mục có trong vectơ gốc.

Nếu vector được cho là xử lý các mục trùng lặp, tôi nghĩ bạn nên thực hiện thói quen sắp xếp của riêng mình.


1

Vâng, giải pháp của tôi sử dụng kỹ thuật dư lượng. Chúng ta có thể đặt các giá trị theo sắp xếp trong 2 byte trên và chỉ số của các phần tử - trong 2 byte thấp hơn:

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

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

Sau đó sắp xếp mảng myintsnhư bình thường:

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

Sau đó, bạn có thể truy cập các chỉ số của các phần tử thông qua phần dư. Đoạn mã sau in các chỉ số của các giá trị được sắp xếp theo thứ tự tăng dần:

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

Tất nhiên, kỹ thuật này chỉ hoạt động đối với các giá trị tương đối nhỏ trong mảng ban đầu myints(tức là những giá trị có thể vừa với 2 byte trên int). Nhưng nó có thêm lợi ích của việc phân biệt các giá trị giống hệt nhau myints: các chỉ số của chúng sẽ được in theo đúng thứ tự.


1

Nếu có thể, bạn có thể xây dựng mảng vị trí bằng hàm find và sau đó sắp xếp mảng.

Hoặc có thể bạn có thể sử dụng bản đồ trong đó khóa sẽ là thành phần và các giá trị danh sách vị trí của nó trong các mảng sắp tới (A, B và C)

Nó phụ thuộc vào việc sử dụng sau này của các mảng.


0

Đối với loại câu hỏi này Lưu trữ dữ liệu mảng gốc vào dữ liệu mới và sau đó tìm kiếm nhị phân phần tử đầu tiên của mảng được sắp xếp vào mảng trùng lặp và chỉ số đó nên được lưu trữ vào một vectơ hoặc mảng.

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

Ở đây binarysearch là một hàm lấy mảng, kích thước của mảng, mục tìm kiếm và sẽ trả về vị trí của mục được tìm kiếm


-1

Có rất nhiều cách. Một giải pháp khá đơn giản là sử dụng vector 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;
}

Đây là đầu ra:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.