Perché qualcuno dovrebbe usare set invece di unordered_set?


145

C ++ 0x sta introducendo unordered_setche è disponibile in booste in molti altri luoghi. Quello che capisco è che unordered_setè una tabella hash con O(1)complessità di ricerca. D'altra parte, setnon è altro che un albero con log(n)complessità di ricerca. Perché mai qualcuno dovrebbe usare setinvece di unordered_set? cioè c'è bisogno di setpiù?


22
La tua domanda è fondamentalmente chiedendo se c'è più bisogno di un albero.
Vinko Vrsalovic,

2
Penso di aver affermato chiaramente in prima linea che questa è in qualche modo una domanda stupida. Mi mancava qualcosa e ora ho ricevuto la risposta :)
AraK,

2
La vera ragione è che le cose non sono così in bianco e nero come sembrano. Ci sono molti grigi e altri colori nel mezzo. Devi ricordare che questi contenitori sono strumenti. A volte le prestazioni non sono cruciali e la praticità è molto più significativa. Se tutti cercassero la soluzione più efficiente non useremmo mai C ++ (per non parlare di Python) in primo luogo e scrivere e ottimizzare continuamente il codice nel linguaggio macchina.
AturSams,

(Perché mai qualcuno dovrebbe usare un nome generico per un'implementazione / interfaccia con promesse oltre a quelle implicite con quel nome, creando una situazione imbarazzante per quelli senza?)
greybeard

Risposte:


219

Quando, per qualcuno che desidera scorrere gli articoli dell'insieme, l'ordine conta.


Viene ordinato in base all'ordine di inserimento o in base al confronto reale utilizzando gli operatori < >?
Qualcosa del

2
È ordinato usando std :: less di default; puoi ignorarlo e fornire il tuo operatore di confronto. cplusplus.com/reference/set/set
moonshadow il

O a volte quando vuoi solo iterare, anche se l'ordine non ha importanza.
mfnx

319

I set non ordinati devono pagare il loro tempo di accesso medio O (1) in alcuni modi:

  • setutilizza meno memoria rispetto unordered_seta memorizzare lo stesso numero di elementi.
  • Per un numero limitato di elementi , le ricerche in a setpotrebbero essere più veloci delle ricerche in a unordered_set.
  • Anche se molte operazioni sono più veloci nel caso medio per unordered_set, sono spesso garantiti per avere una migliore peggiori complessità caso per set(ad esempio insert).
  • Questo set ordinamento degli elementi è utile se si desidera accedervi in ​​ordine.
  • È possibile confrontare lessicografico diversi sets con <, <=, >e >=. unordered_setnon sono richiesti per supportare queste operazioni.


9
+1, tutti i punti eccellenti. Le persone tendono a trascurare il fatto che gli hashtable hanno O (1) tempo di accesso nel caso medio , il che significa che a volte possono avere grandi ritardi. La distinzione può essere importante per i sistemi in tempo reale.
j_random_hacker,

Aspetti positivi , tuttavia qui ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) si afferma che possiamo confrontare unordered_sets.
Michiel ti chiamerà Broek il

5
Definisci un "piccolo numero di elementi"
Sunjay Varma,

4
@SunjayVarma di solito 100 elementi è un buon taglio tra i due. In caso di dubbi, nulla può sostituire le prestazioni di test dei due nel proprio caso d'uso specifico.
Nate,

3
@MichieluithetBroek Viene indicato solo il confronto di uguaglianza, non ordering ( <).
Lisarus

26

Ogni volta che preferisci un albero a una tabella di hash.

Ad esempio, le tabelle hash sono "O (n)" nel caso peggiore. O (1) è il caso medio. Gli alberi sono "O ( log n)" nel peggiore dei casi.


18
/ Equilibrato / alberi sono O (ln n) nel peggiore dei casi. Puoi finire con O (n) alberi (essenzialmente elenchi collegati).
strager

5
Se riesci a scrivere una funzione hash ragionevolmente intelligente, puoi quasi sempre ottenere O (1) perf da una tabella hash. Se non riesci a scrivere una tale funzione di hash se devi iterare "in ordine" sul tuo set, allora dovresti usare un albero. Ma non dovresti usare un albero perché hai paura di "O (n) peggiore delle prestazioni".
Justin L.,

6
stager: essere pedanti, sì. Tuttavia, stiamo parlando di set in C ++ che viene generalmente implementato come un albero di ricerca binaria bilanciato . Avremmo dovuto specificare l'operazione effettiva per parlare di complessità. In questo contesto è ovvio che stiamo parlando di ricerca.
Mehrdad Afshari,

1
Justin L: È solo uno dei motivi per cui potresti preferire un albero. Il nucleo della mia risposta è la prima riga. Ogni volta che si preferisce una struttura di dati ad albero a una tabella hash. Ci sono molti casi in cui gli alberi sono preferiti alle tabelle di hash. Le tabelle hash fanno particolarmente schifo in cose come "intersezioni di distanze".
Mehrdad Afshari,

2
gli alberi di stl sono alberi quasi nero implementati quasi universalmente, un avanzato albero auto-bilanciante. Ci sono davvero casi in cui O (n) cerca nel peggiore dei casi non è accettabile. Un servizio Web che fornisce e si interfaccia per archiviare i valori degli utenti non dovrebbe utilizzare una mappa hash, in quanto un utente malintenzionato potrebbe effettivamente creare un DoS archiviando valori appositamente predisposti. I sistemi critici e sensibili al tempo potrebbero anche non consentire la ricerca O (n), il controllo del traffico aereo ecc. Sebbene in generale tu abbia ragione, usa le mappe di hash di default e cambia la versione dell'albero solo quando ne hai davvero bisogno.
deft_code

14

Usa set quando:

  1. Abbiamo bisogno di dati ordinati (elementi distinti).
  2. Dovremmo stampare / accedere ai dati (in ordine ordinato).
  3. Abbiamo bisogno del predecessore / successore degli elementi.

Usa unordered_set quando:

  1. Dobbiamo mantenere un insieme di elementi distinti e non è richiesto alcun ordine.
  2. Abbiamo bisogno dell'accesso a singolo elemento, cioè senza attraversamento.

Esempi:

impostato:

Ingresso: 1, 8, 2, 5, 3, 9

Uscita: 1, 2, 3, 5, 8, 9

Unordered_set:

Ingresso: 1, 8, 2, 5, 3, 9

Uscita: 9 3 1 8 2 5 (forse questo ordine, influenzato dalla funzione hash)

Principalmente differenza:

inserisci qui la descrizione dell'immagine

Nota: (in alcuni casi setè più conveniente) ad esempio usando vectorcome chiave

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

Il motivo per cui vector<int>può essere la chiave setperché vectoroverride operator<.

Ma se usi unordered_set<vector<int>>devi creare una funzione hash per vector<int>, perché vector non ha una funzione hash, quindi devi definirne una come:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

puoi vedere che in alcuni casi unordered_setè più complicato.

Principalmente citato da: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006


6

Perché std :: set fa parte di Standard C ++ e unordered_set no. C ++ 0x NON è uno standard e nemmeno Boost. Per molti di noi, la portabilità è essenziale e ciò significa attenersi allo standard.


2
Se lo capisco correttamente, non mi sta chiedendo perché le persone attualmente usano ancora set. Si sta informando su C ++ 0x.
Johannes Schaub - litb,

2
Può essere. Pensavo che tutti sapessero che i tavoli e gli alberi di hash risolvevano diversi problemi.

21
Beh, si tratta di uno standard ora (sono voluti solo pochi anni)
Clayton Hughes

6

Prendi in considerazione gli algoritmi sweepline. Questi algoritmi fallirebbero completamente con le tabelle hash, ma funzionerebbero magnificamente con alberi bilanciati. Per darti un esempio concreto di un algoritmo sweepline, considera l'algoritmo di fortuna. http://en.wikipedia.org/wiki/Fortune%27s_algorithm


1
Penso che tale riferimento sia troppo complesso alla luce della domanda. (Ho dovuto cercare)
hectorpal

3

Un'altra cosa, oltre a ciò che altre persone hanno già menzionato. Mentre la complessità ammortizzata prevista per l'inserimento di un elemento in unorder_set è O (1), ogni tanto ci vorrà O (n) perché la tabella hash deve essere ristrutturata (il numero di bucket deve cambiare) - anche con una funzione hash "buona". Proprio come l'inserimento di un elemento in un vettore richiede O (n) di tanto in tanto perché l'array sottostante deve essere riallocato.

L'inserimento in un set richiede sempre al massimo O (log n). Questo potrebbe essere preferibile in alcune applicazioni.


3

Scusatemi, un'altra cosa degna di nota sulla proprietà ordinata:

Se si desidera un intervallo di dati nel contenitore, ad esempio: è stato memorizzato il tempo nel set e si desidera tempo dal 2013-01-01 al 2014-01-01.

Per unordered_set è impossibile.

Naturalmente, questo esempio sarebbe più convincente per i casi d'uso tra map e unordered_map .


3

g++ 6.4 benchmark stdlibc ++ ordinato vs non ordinato

Ho confrontato questa implementazione dominante di Linux C ++ per vedere la differenza:

inserisci qui la descrizione dell'immagine

I dettagli e l'analisi di riferimento completi sono stati forniti su: Qual è la struttura di dati sottostante di un set STL in C ++? e non li ripeterò qui.

"BST" significa "testato con std::sete" hash map "significa" testato con std::unordered_set. "Heap" è per il std::priority_queuequale ho analizzato su: Heap vs Binary Search Tree (BST)

Come breve riepilogo:

  • il grafico mostra chiaramente che in queste condizioni, l'inserimento di hashmap è sempre stato molto più veloce quando ci sono più di 100k articoli e la differenza aumenta all'aumentare del numero di elementi

    Il costo di questo aumento di velocità è che non sei in grado di attraversare in modo efficiente in ordine.

  • le curve suggeriscono chiaramente che ordinato std::setè basato su BST e basato su std::unordered_sethashmap. Nella risposta di riferimento, ho ulteriormente confermato che tramite il passaggio GDB il debug del codice.

Domanda simile per mapvs unordered_map: c'è qualche vantaggio nell'usare map over unordered_map in caso di chiavi banali?


1

Fuori mano, direi che è conveniente avere delle cose in una relazione se stai cercando di convertirla in un formato diverso.

È anche possibile che mentre si accede più velocemente, il tempo per creare l'indice o la memoria utilizzata durante la creazione e / o l'accesso ad esso sia maggiore.


+1, la notazione Big Oh nasconde i fattori costanti e per le dimensioni tipiche del problema sono spesso i fattori costanti che contano di più.
j_random_hacker,

1

Se vuoi che le cose vengano ordinate, allora useresti set invece di unordered_set. unordered_set viene utilizzato su set quando l'ordinamento memorizzato non ha importanza.


1

Mentre questa risposta potrebbe essere in ritardo di 10 anni, vale la pena sottolineare che std::unordered_setha anche degli svantaggi di sicurezza.

Se la funzione hash è prevedibile (questo è in genere il caso a meno che non applichi contromisure come un salt randomizzato), gli aggressori possono creare manualmente dati che producono collisioni di hash e fanno in modo che tutti gli inserimenti e le ricerche impieghino il tempo O (n) .

Questo può essere usato per attacchi denial-of-service molto efficienti ed eleganti.

Molte (la maggior parte?) Implementazioni di lingue che impiegano internamente mappe hash si sono imbattute in questo:

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.