Pourquoi quelqu'un utiliserait-il set au lieu de unordered_set?

145

C ++ 0x introduit unordered_setce qui est disponible dans boostet dans de nombreux autres endroits. Ce que je comprends, c'est qu'il unordered_sets'agit d'une table de hachage avec une O(1)complexité de recherche. D'un autre côté, ce setn'est rien d'autre qu'un arbre avec une log(n)complexité de recherche. Pourquoi diable quelqu'un utiliserait-il à la setplace de unordered_set? c'est à dire y a-t-il un besoin de setplus?

AraK
la source
22
Votre question est fondamentalement de savoir si un arbre est plus nécessaire.
Vinko Vrsalovic
2
Je pense que je l'ai dit clairement dans la première ligne, que c'est en quelque sorte une question stupide. Il me manquait quelque chose et maintenant j'ai eu la réponse :)
AraK
2
La vraie raison est que les choses ne sont pas aussi N&B qu'elles le paraissent. Il y a beaucoup de gris et d'autres couleurs entre les deux. Vous devez vous rappeler que ces conteneurs sont des outils. Parfois, les performances ne sont pas cruciales et la commodité est beaucoup plus significative. Si les gens recherchaient tous la solution la plus efficace, nous n'utiliserions jamais C ++ (sans parler de Python) en premier lieu et
écrivions
(Pourquoi diable quelqu'un utiliserait-il un nom générique pour une implémentation / interface avec des promesses au-delà de celles impliquées par ce nom, créant une situation délicate pour ceux qui n'en ont pas?)
greybeard

Réponses:

219

Quand, pour quelqu'un qui souhaite parcourir les éléments de l'ensemble, l'ordre compte.

l'ombre de la lune
la source
Est-il ordonné selon l'ordre d'insertion, ou selon une comparaison réelle à l'aide d'opérateurs < >?
SomethingSomething
2
Il est commandé en utilisant std :: less par défaut; vous pouvez le remplacer et fournir votre propre opérateur de comparaison. cplusplus.com/reference/set/set
moonshadow
Ou parfois lorsque vous ne voulez qu'itérer, même si l'ordre n'a pas d'importance.
mfnx le
319

Les postes non ordonnés doivent payer leur temps d'accès moyen O (1) de plusieurs manières:

  • setutilise moins de mémoire que unordered_setpour stocker le même nombre d'éléments.
  • Pour un petit nombre d'éléments , les recherches dans un fichier setpeuvent être plus rapides que les recherches dans un fichier unordered_set.
  • Même si de nombreuses opérations sont plus rapides dans le cas moyen pour unordered_set, ils sont souvent assurés d'avoir de meilleures pires complexité des cas pour set(par exemple insert).
  • Cela set trie les éléments est utile si vous souhaitez y accéder dans l'ordre.
  • Vous pouvez lexicographique comparer différentes sets avec <, <=, >et >=. unordered_setne sont pas tenus de soutenir ces opérations.

qc
la source
9
+1, tous d'excellents points. Les gens ont tendance à négliger le fait que les tables de hachage ont un temps d'accès moyen O (1) , ce qui signifie qu'ils peuvent parfois avoir de gros retards. La distinction peut être importante pour les systèmes en temps réel.
j_random_hacker
Bons points, cependant ici ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) il est indiqué que nous pouvons comparer unordered_sets.
Michiel uit het Broek
5
Définir un "petit nombre d'éléments"
Sunjay Varma
4
@SunjayVarma généralement 100 éléments est une bonne coupure entre les deux. En cas de doute, rien ne peut remplacer les performances de test des deux dans votre cas d'utilisation spécifique.
Nate le
3
@MichieluithetBroek Seule la comparaison d'égalité est indiquée, pas ordering ( <).
lisyarus
26

Chaque fois que vous préférez un arbre à une table de hachage.

Par exemple, les tables de hachage sont "O (n)" dans le pire des cas. O (1) est le cas moyen. Les arbres sont au pire "O ( log n)".

Mehrdad Afshari
la source
18
/ Balanced / arbres sont O (ln n) dans le pire des cas. Vous pouvez vous retrouver avec des arbres O (n) (essentiellement des listes liées).
strager
5
Si vous pouvez écrire une fonction de hachage raisonnablement intelligente, vous pouvez presque toujours extraire O (1) perf d'une table de hachage. Si vous ne pouvez pas écrire une telle fonction de hachage ou si vous avez besoin d'itérer «dans l'ordre» sur votre ensemble, vous devez utiliser un arbre. Mais vous ne devriez pas utiliser un arbre parce que vous avez peur des «performances dans le pire des cas O (n)».
Justin L.
6
stager: Pour être pédant, oui. Cependant, nous parlons de set en C ++ qui est généralement implémenté sous la forme d' un arbre de recherche binaire équilibré . Nous aurions dû préciser l'opération réelle pour parler de complexité. Dans ce contexte, il est évident que nous parlons de recherche.
Mehrdad Afshari
1
Justin L: C'est juste une des raisons pour lesquelles vous pourriez préférer un arbre. Le cœur de ma réponse est la première ligne. Chaque fois que vous préférez une structure de données arborescente à une table de hachage. Il existe de nombreux cas où les arbres sont préférés aux tables de hachage. Les tables de hachage sont particulièrement nulles pour des choses comme les «intersections de plage».
Mehrdad Afshari
2
Les arbres stl sont des arbres rouge-noir presque universellement mis en œuvre, un arbre à équilibrage automatique avancé. Il y a vraiment des cas où O (n) rechercher dans le pire des cas n'est pas acceptable. Un service Web qui fournit une interface pour stocker les valeurs utilisateur ne doit pas utiliser de mappage de hachage, car un utilisateur malveillant pourrait effectivement créer un DoS en stockant des valeurs spécialement conçues. Les systèmes critiques et sensibles au temps peuvent également ne pas permettre la recherche O (n), le contrôle du trafic aérien, etc.
deft_code le
14

Utilisez set lorsque:

  1. Nous avons besoin de données ordonnées (éléments distincts).
  2. Nous aurions à imprimer / accéder aux données (dans l'ordre trié).
  3. Nous avons besoin d'un prédécesseur / successeur d'éléments.

Utilisez unordered_set lorsque:

  1. Nous devons conserver un ensemble d'éléments distincts et aucune commande n'est requise.
  2. Nous avons besoin d'un accès à un élément, c'est-à-dire pas de traversée.

Exemples:

ensemble:

Entrée: 1, 8, 2, 5, 3, 9

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

Unordered_set:

Entrée: 1, 8, 2, 5, 3, 9

Sortie: 9 3 1 8 2 5 (peut-être cet ordre, influencé par la fonction de hachage)

Principalement différence:

entrez la description de l'image ici

Remarque: (dans certains cas, setc'est plus pratique) par exemple en utilisant vectorcomme clé

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 

La raison pour laquelle vector<int>peut être aussi clé setque le vectorremplacement operator<.

Mais si vous utilisez, unordered_set<vector<int>>vous devez créer une fonction de hachage pour vector<int>, car vector n'a pas de fonction de hachage, vous devez donc en définir une comme:

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
}

vous pouvez voir que dans certains cas, unordered_setc'est plus compliqué.

Principalement cité à partir de: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006

Jayhello
la source
6

Parce que std :: set fait partie du C ++ standard et unordered_set ne l'est pas. C ++ 0x n'est PAS un standard, et Boost non plus. Pour beaucoup d'entre nous, la portabilité est essentielle, ce qui signifie s'en tenir à la norme.


la source
2
Si je le comprends bien, il ne demande pas pourquoi les gens utilisent encore actuellement set. Il s'informe sur C ++ 0x.
Johannes Schaub - litb
2
Peut être. Je pensais que tout le monde savait que les tables de hachage et les arbres résolvaient différents problèmes.
21
Eh bien, c'est une norme maintenant (cela n'a pris que quelques années)
Clayton Hughes
6

Considérez les algorithmes Sweepline. Ces algorithmes échoueraient complètement avec les tables de hachage, mais fonctionneraient parfaitement avec des arbres équilibrés. Pour vous donner un exemple concret d'algorithme sweepline, considérons l'algorithme de fortune. http://en.wikipedia.org/wiki/Fortune%27s_algorithm

ldog
la source
1
Je pense qu'une telle référence est trop complexe compte tenu de la question. (Je devais le rechercher)
hectorpal
3

Encore une chose, en plus de ce que d'autres personnes ont déjà mentionné. Bien que la complexité après amortissement prévu pour l' insertion d' un élément à un unordered_set est O (1), chaque maintenant et il va prendre O (n) parce que les besoins de table de hachage à restructurées (le nombre de seaux besoins de changement) - même avec une «bonne» fonction de hachage. Tout comme l'insertion d'un élément dans un vecteur prend O (n) de temps en temps car le tableau sous-jacent doit être réalloué.

L'insertion dans un ensemble prend toujours au plus O (log n). Cela peut être préférable dans certaines applications.

Blargle
la source
3

Pardonnez-moi, encore une chose à noter à propos de la propriété triée:

Si vous voulez une plage de données dans le conteneur, par exemple: Vous avez stocké l'heure dans l' ensemble et vous voulez une heure du 01/01/2013 au 01/01/2014.

Pour unordered_set, c'est impossible.

Bien sûr, cet exemple serait plus convaincant pour les cas d'utilisation entre map et unordered_map .

Spectral
la source
3

g++ 6.4 Stdlibc ++ Benchmark ordonné vs non ordonné

J'ai comparé cette implémentation Linux C ++ dominante pour voir la différence:

entrez la description de l'image ici

Les détails et l'analyse complets du benchmark ont ​​été donnés à l' adresse : Quelle est la structure de données sous-jacente d'un ensemble STL en C ++? et je ne les répéterai pas ici.

"BST" signifie "testé avec std::setet" hash map "signifie" testé avec std::unordered_set. "Heap" est pour std::priority_queuelequel j'ai analysé à: Heap vs Binary Search Tree (BST)

En résumé:

  • le graphique montre clairement que dans ces conditions, l'insertion de hashmap était toujours beaucoup plus rapide lorsqu'il y avait plus de 100k éléments, et la différence augmente à mesure que le nombre d'éléments augmente

    Le coût de cette augmentation de vitesse est que vous n'êtes pas en mesure de traverser efficacement dans l'ordre.

  • les courbes suggèrent clairement que ordonné std::setest basé sur BST et std::unordered_setest basé sur hashmap. Dans la réponse de référence, j'ai en outre confirmé cela en déboguant le code par étape GDB.

Question similaire pour mapvs unordered_map: y a-t-il un avantage à utiliser map sur unordered_map en cas de clés triviales?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
1

Au loin, je dirais qu'il est pratique d'avoir des choses dans une relation si vous cherchez à le convertir dans un format différent.

Il est également possible que si l'accès est plus rapide, le temps de construction de l'index ou la mémoire utilisée lors de sa création et / ou de son accès soit plus long.

Rushyo
la source
+1, la notation Big Oh masque les facteurs constants, et pour les tailles de problème typiques, ce sont souvent les facteurs constants qui comptent le plus.
j_random_hacker
1

Si vous voulez que les choses soient triées, vous utiliserez set au lieu de unordered_set. unordered_set est utilisé sur set lorsque la commande stockée n'a pas d'importance.

leiz
la source
1

Bien que cette réponse ait peut-être 10 ans de retard, il convient de souligner qu'elle std::unordered_setprésente également des inconvénients en matière de sécurité.

Si la fonction de hachage est prévisible (c'est généralement le cas sauf si elle applique des contre-mesures telles qu'un sel aléatoire), les attaquants peuvent fabriquer à la main des données qui produisent des collisions de hachage et font que toutes les insertions et recherches prennent O (n) temps .

Cela peut être utilisé pour des attaques par déni de service très efficaces et élégantes.

De nombreuses (la plupart?) Implémentations de langages qui utilisent en interne des cartes de hachage se sont heurtées à ceci:

mic_e
la source