การเรียงลำดับ C ++ และการติดตามดัชนี


216

ใช้ 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

ทุกคนสามารถเห็นวิธีที่ดีในการทำเช่นนี้?

คำตอบ:


298

ใช้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;
}

ตอนนี้คุณสามารถใช้เวกเตอร์ดัชนีที่ส่งคืนในการวนซ้ำเช่น

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

คุณยังสามารถเลือกที่จะจัดหาเวกเตอร์ดัชนีดั้งเดิมของคุณฟังก์ชันเรียงลำดับตัวเปรียบเทียบหรือเรียงลำดับใหม่ v ในฟังก์ชัน sort_indexes โดยใช้เวกเตอร์พิเศษ


4
รักคำตอบนี้ถ้าคอมไพเลอร์ของคุณไม่รองรับ lambdas คุณสามารถใช้ class: template <typename T> class CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public: CompareIndicesByAnotherVectorValues ​​(std :: vector <T> * ค่า): _values ​​(ค่า) {} public: ตัวดำเนินการ bool () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ b]; }};
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

คุณสามารถเรียงลำดับ std :: pair แทนเพียง ints - 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

ผลลัพธ์ของ std :: sort บน v_prime โดยใช้ตัวเปรียบเทียบนั้นควรเป็น:

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

คุณสามารถลอกดัชนีออกมาได้โดยการเดินเวกเตอร์จับ. วินาทีจากคู่แต่ละคู่ ::


1
นี่เป็นวิธีที่ฉันจะทำเช่นกัน ฟังก์ชั่นการจัดเรียงพื้นฐานไม่ได้ติดตามตำแหน่งเก่ากับตำแหน่งใหม่เนื่องจากจะเพิ่มค่าใช้จ่ายที่ไม่จำเป็นเพิ่มเติม
the_mandrill

8
ข้อเสียของฟังก์ชันนี้คือคุณต้องจัดสรรหน่วยความจำใหม่สำหรับค่าทั้งหมด
Yoav

1
เห็นได้ชัดว่านี่เป็นวิธีที่ใช้การได้ แต่มีข้อเสียที่คุณต้องเปลี่ยนคอนเทนเนอร์เดิมของคุณจาก "container of numbers" เป็น "container of pairs"
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()สำหรับการเริ่มต้นที่หรูหรายิ่งขึ้นของ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 as

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 = valueที่i'th

a[i].second = idx ในอาร์เรย์เริ่มต้น


ลองเพิ่มคำอธิบายรหัสของคุณเพื่อให้ผู้ใช้ที่เข้าชมโพสต์นี้สามารถเข้าใจวิธีการใช้งาน
BusyProgrammer

ฉันชอบโซลูชันนี้ดีที่สุด - เวกเตอร์ของฉันมีขนาด 4 หรือมากกว่านั้นและฉันติดอยู่กับ C ++ 11 และไม่สามารถใช้ lambdas ได้ ขอบคุณ Aditya Aswal
stephanmg

6

ฉันเจอคำถามนี้และคิดว่าการเรียงลำดับตัววนซ้ำโดยตรงจะเป็นวิธีในการเรียงลำดับค่าและติดตามดัชนี ไม่จำเป็นต้องกำหนดคอนเทนเนอร์พิเศษของpairs ของ (ค่าดัชนี) ซึ่งมีประโยชน์เมื่อค่าเป็นวัตถุขนาดใหญ่ ตัววนซ้ำให้การเข้าถึงทั้งค่าและดัชนี:

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

สิ่งนี้จะกำจัดองค์ประกอบที่ไม่ซ้ำกันออกไป หากไม่เป็นที่ยอมรับให้ใช้ 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()));

ในการแสดงผลดัชนีให้ทำซ้ำบนแผนที่หรือหลายแผนที่:

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คืออนุญาตค่าที่เท่ากันในเวกเตอร์ดั้งเดิม นอกจากนี้ยังโปรดทราบว่าแตกต่างสำหรับ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;
}

ideone


1

ไอเท็มในเวกเตอร์นั้นมีความพิเศษหรือไม่? ถ้าเป็นเช่นนั้นให้คัดลอกเวกเตอร์เรียงลำดับหนึ่งในสำเนาด้วยSTL Sortจากนั้นคุณสามารถค้นหาดัชนีที่แต่ละรายการมีในเวกเตอร์ดั้งเดิม

หากเวกเตอร์ควรจัดการรายการที่ซ้ำกันฉันคิดว่าคุณควรนำชุดคำสั่งการเรียงลำดับของคุณไปใช้


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

ถ้าเป็นไปได้คุณสามารถสร้างอาร์เรย์ตำแหน่งโดยใช้ฟังก์ชั่นค้นหาแล้วเรียงลำดับอาร์เรย์

หรือบางทีคุณสามารถใช้แผนที่ซึ่งกุญแจจะเป็นองค์ประกอบและค่ารายการตำแหน่งในอาร์เรย์ที่กำลังจะมาถึง (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]));`

ที่นี่ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.