Ordinamento in C ++ e monitoraggio degli indici


225

Utilizzando C ++ e, si spera, la libreria standard, voglio ordinare una sequenza di campioni in ordine crescente, ma voglio anche ricordare gli indici originali dei nuovi campioni.

Ad esempio, ho un set, un vettore o una matrice di campioni A : [5, 2, 1, 4, 3]. Voglio ordinarli in modo che siano B : [1,2,3,4,5], ma voglio anche ricordare gli indici originali dei valori, così posso ottenere un altro set che sarebbe: C : [2, 1, 4, 3, 0 ]- che corrisponde all'indice di ogni elemento in "B", nell'originale "A '.

Ad esempio, in Matlab puoi fare:

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

Qualcuno vede un buon modo per farlo?

Risposte:


309

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

Ora puoi usare il vettore indice restituito in iterazioni come

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

Puoi anche scegliere di fornire il tuo vettore indice originale, la funzione di ordinamento, il comparatore o riordinare automaticamente v nella funzione sort_indexes utilizzando un vettore aggiuntivo.


4
Adoro questa risposta. Se il tuo compilatore non supporta lambda, puoi usare una classe: 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) [ b]; }};
Yoav

2
Adoro anche questa risposta, non è necessario copiare il vettore originale per creare il vettore delle coppie.
headmyshoulder

29
Piuttosto che artigianale, for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;preferisco lo standardstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
utilizzare #include <numeric>per iota ()
kartikag01

7
iotaè l'algoritmo con il nome meno ovvio dell'intera libreria standard C ++.
Seth Johnson,

88

Puoi ordinare std :: pair invece di ints: il primo int è i dati originali, il secondo int è l'indice originale. Quindi fornire un comparatore che ordina solo sul primo int. Esempio:

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

Ordina la nuova istanza del problema utilizzando un comparatore come:

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

Il risultato di std :: sort su v_prime, utilizzando quel comparatore, dovrebbe essere:

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

Puoi rimuovere gli indici camminando sul vettore, afferrando .second da ogni coppia std ::.


1
Questo è esattamente come lo farei anch'io. La funzione di ordinamento di base non tiene traccia delle vecchie e nuove posizioni in quanto ciò aggiungerebbe un sovraccarico non necessario.
the_mandrill

8
Lo svantaggio di questa funzione è che richiede di riallocare la memoria per tutti i valori.
Yoav

1
Questo è ovviamente un approccio praticabile, ma ha uno svantaggio che devi cambiare il tuo contenitore originale da "contenitore di numeri" a "contenitore di coppie".
Ruslan

23

Supponiamo che il vettore Dato sia

A=[2,4,3]

Crea un nuovo vettore

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

Ordina V e durante l'ordinamento invece di confrontare gli elementi di V, confronta gli elementi corrispondenti di 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];} );

Amo la tua risposta. puoi anche usare std::iota()per un'inizializzazione più elegante dimap
Nimrod Morag

Sì, possiamo usarlo! Grazie per il suggerimento
MysticForce

12

Ho scritto una versione generica di index sort.

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

L'utilizzo è uguale a quello di std :: sort tranne per un contenitore di indici per ricevere gli indici ordinati. test:

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

dovresti ottenere 2 1 0 3. per i compilatori senza supporto c ++ 0x, sostituire l'espressione lamba come modello di classe:

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

e riscrivi std :: sort come

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

Ciao hkyi! Come istanziamo questa funzione modello? Ha due nomi di modello di modello e uno di questi è un iteratore che rende questa situazione molto rara. Potresti aiutare?
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";
}

Ora acontiene sia i nostri valori che i rispettivi indici nel file ordinato.

a[i].first = valuea i'th.

a[i].second = idx nell'array iniziale.


Considera l'idea di aggiungere una descrizione del tuo codice in modo che gli utenti che visitano questo post possano capire come funziona.
BusyProgrammer

In realtà mi piace di più questa soluzione: il mio vettore è di dimensione 4 o giù di lì e sono bloccato prima di C ++ 11 e non posso usare lambda. Grazie Aditya Aswal.
stephanmg

6

Mi sono imbattuto in questa domanda e ho capito che ordinare gli iteratori direttamente sarebbe stato un modo per ordinare i valori e tenere traccia degli indici; Non è necessario definire un contenitore aggiuntivo di pairs of (value, index) che è utile quando i valori sono oggetti di grandi dimensioni; Gli iteratori forniscono l'accesso sia al valore che all'indice:

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

per quanto riguarda l'esempio di utilizzo:

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

ora, ad esempio, il quinto elemento più piccolo nel vettore ordinato avrebbe valore **idx[ 5 ]e il suo indice nel vettore originale sarebbe distance( A.begin( ), *idx[ 5 ] )o semplicemente *idx[ 5 ] - A.begin( ).


3

C'è un altro modo per risolvere questo problema, usando una mappa:

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

Tuttavia, questo eliminerà elementi non unici. Se ciò non è accettabile, utilizza una mappa multipla:

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

Per produrre gli indici, iterare sulla mappa o multimappa:

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

3

Bella soluzione di @Lukasz Wiklendt! Anche se nel mio caso avevo bisogno di qualcosa di più generico, quindi l'ho modificato un po ':

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

Esempio: trova indici che ordinano un vettore di stringhe in base alla lunghezza, ad eccezione del primo elemento che è un fittizio.

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;

stampe:

abc
ab
a

3

Considera l'utilizzo std::multimapcome suggerito da @Ulrich Eckhardt. Solo che il codice potrebbe essere reso ancora più semplice.

Dato

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

Ordinare nel frattempo dell'inserimento

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

Per recuperare valori e indici originali

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
}

Il motivo per preferire a std::multimapad a std::mapè consentire valori uguali nei vettori originali. Si noti inoltre che, a differenza di for std::map, operator[]non è definito per std::multimap.


2

Crea una std::pairfunzione in poi ordina la coppia:

versione generica:

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

Gli elementi nel vettore sono unici? In tal caso, copia il vettore, ordina una delle copie con STL Sort, quindi puoi trovare l'indice che ogni elemento aveva nel vettore originale.

Se il vettore dovrebbe gestire elementi duplicati, penso che tu sia meglio implementare la tua routine di ordinamento.


1

Ebbene, la mia soluzione utilizza la tecnica dei residui. Possiamo posizionare i valori sotto ordinamento nei 2 byte superiori e gli indici degli elementi - nei 2 byte inferiori:

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

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

Quindi ordina l'array myintscome al solito:

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

Successivamente è possibile accedere agli indici degli elementi tramite residuum. Il codice seguente stampa gli indici dei valori ordinati in ordine crescente:

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

Naturalmente, questa tecnica funziona solo per i valori relativamente piccoli nell'array originale myints(cioè quelli che possono entrare nei 2 byte superiori di int). Ma ha un ulteriore vantaggio di distinguere valori identici di myints: i loro indici verranno stampati nell'ordine corretto.


1

Se è possibile, puoi costruire l'array di posizione usando la funzione find e quindi ordinare l'array.

O forse puoi usare una mappa in cui la chiave sarebbe l'elemento e i valori un elenco della sua posizione negli array futuri (A, B e C)

Dipende dagli usi successivi di questi array.


0

Per questo tipo di domande Memorizza i dati dell'array originale in un nuovo dato, quindi cerca in modo binario il primo elemento dell'array ordinato nell'array duplicato e quell'indice deve essere memorizzato in un vettore o in un array.

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

Qui binarysearchèuna funzione che prende l'array, la dimensione dell'array, l'elemento di ricerca e restituisce la posizione dell'elemento cercato


-1

Ci sono molti modi. Una soluzione piuttosto semplice è utilizzare un vettore 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;
}

Ecco l'output:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.