2013-02-15 14 views
12

Ho bisogno di trovare gli indici dei k più grandi elementi di una lunghezza non ordinata n, matrice/vettore in C++, con k < n. Ho visto come usare nth_element() per trovare la statistica k-th, ma non sono sicuro che l'uso di questo sia la scelta giusta per il mio problema in quanto sembra che avrei bisogno di effettuare k chiamate a nth_statistic, che immagino avrebbe complessità O (kn), che può essere buono come si può ottenere? O c'è un modo per farlo solo in O (n)?indici dei k più grandi elementi in una lunghezza non ordinata n array

L'implementazione senza nth_element() sembra che dovrò iterare su tutto l'array una volta, compilando un elenco di indici degli elementi più grandi ad ogni passaggio.

C'è qualcosa nella libreria standard C++ che rende questo un one-liner o un modo intelligente per implementarlo da solo in un paio di righe? Nel mio caso particolare, k = 3 e n = 6, quindi l'efficienza non è un problema enorme, ma sarebbe bello trovare un modo pulito ed efficiente per fare questo per k e n arbitrari.

Sembra che Mark the top N elements of an unsorted array sia probabilmente il post più vicino che riesco a trovare su SO, i post presenti in Python e PHP.

+0

È possibile modificare il vettore? nth_element eseguirà un ordinamento parziale, quindi modifica il vettore. – amdn

+0

Il vettore può essere modificato, tuttavia il risultato finale deve essere l'indice (del vettore originale) degli elementi k più grandi. – hazelnusse

+0

Questo è solo un algoritmo di selezione. Di solito utilizzerai la selezione heap o la selezione rapida. Vedi http://stackoverflow.com/q/7746648/56778 per una domanda simile. C'è una risposta con una buona soluzione C++. (usando priority_queue) –

risposta

3

È possibile utilizzare la base dell'algoritmo quicksort per fare ciò che è necessario, ma invece di riordinare le partizioni, è possibile eliminare le voci che escono dall'intervallo desiderato.

E 'stato denominato "Selezione rapida" e here is a C++ implementation:

int partition(int* input, int p, int r) 
{ 
    int pivot = input[r]; 

    while (p < r) 
    { 
     while (input[p] < pivot) 
      p++; 

     while (input[r] > pivot) 
      r--; 

     if (input[p] == input[r]) 
      p++; 
     else if (p < r) { 
      int tmp = input[p]; 
      input[p] = input[r]; 
      input[r] = tmp; 
     } 
    } 

    return r; 
} 

int quick_select(int* input, int p, int r, int k) 
{ 
    if (p == r) return input[p]; 
    int j = partition(input, p, r); 
    int length = j - p + 1; 
    if (length == k) return input[j]; 
    else if (k < length) return quick_select(input, p, j - 1, k); 
    else return quick_select(input, j + 1, r, k - length); 
} 

int main() 
{ 
    int A1[] = { 100, 400, 300, 500, 200 }; 
    cout << "1st order element " << quick_select(A1, 0, 4, 1) << endl; 
    int A2[] = { 100, 400, 300, 500, 200 }; 
    cout << "2nd order element " << quick_select(A2, 0, 4, 2) << endl; 
    int A3[] = { 100, 400, 300, 500, 200 }; 
    cout << "3rd order element " << quick_select(A3, 0, 4, 3) << endl; 
    int A4[] = { 100, 400, 300, 500, 200 }; 
    cout << "4th order element " << quick_select(A4, 0, 4, 4) << endl; 
    int A5[] = { 100, 400, 300, 500, 200 }; 
    cout << "5th order element " << quick_select(A5, 0, 4, 5) << endl; 
} 

USCITA:

1st order element 100 
2nd order element 200 
3rd order element 300 
4th order element 400 
5th order element 500 

EDIT

Questo particolare implementazione ha un O (n) tempo medio di esecuzione; a causa del metodo di selezione di pivot, condivide il tempo di esecuzione nel caso peggiore di quicksort. Con optimizing the pivot choice, anche il tuo caso peggiore diventa O (n).

1

La libreria standard non otterrà un elenco di indici (è stato progettato per evitare il passaggio di dati ridondanti). Tuttavia, se siete interessati a n elementi più grandi, utilizzare un qualche tipo di partizionamento (sia std::partition e std::nth_element sono O (n)):

#include <iostream> 
#include <algorithm> 
#include <vector> 

struct Pred { 
    Pred(int nth) : nth(nth) {}; 
    bool operator()(int k) { return k >= nth; } 
    int nth; 
}; 

int main() { 

    int n = 4; 
    std::vector<int> v = {5, 12, 27, 9, 4, 7, 2, 1, 8, 13, 1}; 

    // Moves the nth element to the nth from the end position. 
    std::nth_element(v.begin(), v.end() - n, v.end()); 

    // Reorders the range, so that the first n elements would be >= nth. 
    std::partition(v.begin(), v.end(), Pred(*(v.end() - n))); 

    for (auto it = v.begin(); it != v.end(); ++it) 
     std::cout << *it << " "; 
    std::cout << "\n"; 

    return 0; 
} 
+0

Ho specificamente bisogno degli indici. – hazelnusse

+0

@hazelnusse È possibile definire un tipo di struttura per i propri elementi, memorizzando sia il valore che l'indice originale e, nel frattempo, definire il comparatore per esso. – ziyuang

8

Ecco la mia implementazione che fa quello che voglio e penso sia ragionevolmente efficiente:

#include <queue> 
#include <vector> 
// maxindices.cc 
// compile with: 
// g++ -std=c++11 maxindices.cc -o maxindices 
int main() 
{ 
    std::vector<double> test = {0.2, 1.0, 0.01, 3.0, 0.002, -1.0, -20}; 
    std::priority_queue<std::pair<double, int>> q; 
    for (int i = 0; i < test.size(); ++i) { 
    q.push(std::pair<double, int>(test[i], i)); 
    } 
    int k = 3; // number of indices we need 
    for (int i = 0; i < k; ++i) { 
    int ki = q.top().second; 
    std::cout << "index[" << i << "] = " << ki << std::endl; 
    q.pop(); 
    } 
} 

che fornisce in uscita:

index[0] = 3 
index[1] = 1 
index[2] = 0 
+2

Ho programmato un'implementazione utilizzando nth_element e una con partial_sort e utilizzando un comparatore personalizzato ... la tua implementazione è più veloce. – amdn

+6

Non è necessario aggiungere tutti gli elementi alla coda di priorità. Questo rende l'algoritmo O (n log n). Può essere fatto in O (n log k) se non aggiungi cose che sono più piccole dell'elemento più piccolo già in coda. Vedi http://stackoverflow.com/q/7746648/56778 per la discussione. –

+0

@JimMischel Forse mi manca qualcosa, ma per quanto posso vedere, se aggiungo solo elementi che sono più grandi del più piccolo elemento della coda, potrei finire per perdere alcuni degli elementi k-top. E.g se il primo elemento che aggiungo alla coda di priorità è l'elemento massimo, è allo stesso tempo l'elemento più piccolo nella coda e comporterebbe l'algoritmo che non aggiunge alcun elemento aggiuntivo. – spurra

6

la domanda ha la risposta parziale; ovvero std::nth_element restituisce "la statistica n-esima" con una proprietà che nessuno degli elementi che precedono l'uno è maggiore di quello e nessuno degli elementi che lo seguono è inferiore a.

Pertanto, è sufficiente una chiamata a std::nth_element per ottenere gli elementi k più grandi. La complessità temporale sarà O (n) che è teoricamente la più piccola dal momento che devi visitare ogni elemento almeno una volta per trovare gli elementi più piccoli (o in questo caso k-più piccoli). Se hai bisogno di questi k elementi da ordinare, devi ordinarli che saranno O (k log (k)). Quindi, in totale O (n + k log (k)).

+3

Questo trova gli elementi k più grandi, mentre il requisito dell'OP è trovare i k indici più grandi. –

+3

Beh, hai ragione e (guardando di nuovo la domanda) non so perché ho dato questa risposta in primo luogo e perché la gente ha votato. Ma molto probabilmente, hanno frainteso la domanda proprio come me, e apparentemente, questa risposta li ha aiutati in qualche modo quindi la terrò così. –

4

Questo dovrebbe essere una versione migliorata del @hazelnusse che viene eseguita in O(nlogk) anziché O(nlogn)

#include <queue> 
#include <iostream> 
#include <vector> 
// maxindices.cc 
// compile with: 
// g++ -std=c++11 maxindices.cc -o maxindices 
int main() 
{ 
    std::vector<double> test = {2, 8, 7, 5, 9, 3, 6, 1, 10, 4}; 
    std::priority_queue< std::pair<double, int>, std::vector< std::pair<double, int> >, std::greater <std::pair<double, int> > > q; 
    int k = 5; // number of indices we need 
    for (int i = 0; i < test.size(); ++i) { 
    if(q.size()<k) 
     q.push(std::pair<double, int>(test[i], i)); 
    else if(q.top().first < test[i]){ 
     q.pop(); 
     q.push(std::pair<double, int>(test[i], i)); 
    } 
    } 
    k = q.size(); 
    std::vector<int> res(k); 
    for (int i = 0; i < k; ++i) { 
    res[k - i - 1] = q.top().second; 
    q.pop(); 
    } 
    for (int i = 0; i < k; ++i) { 
    std::cout<< res[i] <<std::endl; 
    } 
} 
0

È possibile farlo in O(n) tempo con un singolo calcolo statistico dell'ordine:

  • Sia r essere la statistica k ordine -esimo
  • inizializzazione due liste vuote bigger e equal.
  • Per ogni indice i:
    • Se array[i] > r, aggiungere i-bigger
    • Se array[i] = r, aggiungere i a equal
  • elementi Elimina Da equal fino a quando la somma delle lunghezze delle due liste è k
  • Restituisce la concatenazione delle due liste.

Naturalmente, è necessario un solo elenco se tutti gli elementi sono distinti. E se necessario, potresti fare trucchi per combinare le due liste in una sola, anche se ciò renderebbe il codice più complicato.

0

Anche se il codice seguente potrebbe non soddisfare i vincoli di complessità desiderati, potrebbe essere un'alternativa interessante per la coda di priorità menzionata in precedenza.

#include <queue> 
#include <vector> 
#include <iostream> 
#include <iterator> 
#include <algorithm> 

std::vector<int> largestIndices(const std::vector<double>& values, int k) { 
    std::vector<int> ret; 

    std::vector<std::pair<double, int>> q; 
    int index = -1; 
    std::transform(values.begin(), values.end(), std::back_inserter(q), [&](double val) {return std::make_pair(val, ++index); }); 
    auto functor = [](const std::pair<double, int>& a, const std::pair<double, int>& b) { return b.first > a.first; }; 
    std::make_heap(q.begin(), q.end(), functor); 
    for (auto i = 0; i < k && i<values.size(); i++) { 
     std::pop_heap(q.begin(), q.end(), functor); 
     ret.push_back(q.back().second); 
     q.pop_back(); 
    } 

    return ret; 
} 

int main() 
{ 
    std::vector<double> values = { 7,6,3,4,5,2,1,0 }; 
    auto ret=largestIndices(values, 4); 
    std::copy(ret.begin(), ret.end(), std::ostream_iterator<int>(std::cout, "\n")); 
}