web-dev-qa-db-fra.com

Quelle est la structure de données sous-jacente d'un ensemble STL en C ++?

Je voudrais savoir comment un ensemble est implémenté en C++. Si je devais implémenter mon propre conteneur d'ensemble sans utiliser le conteneur fourni par STL, quelle serait la meilleure façon de procéder?

Je comprends que les ensembles STL sont basés sur la structure de données abstraite d'un arbre de recherche binaire. Quelle est donc la structure de données sous-jacente? Un tableau?

De plus, comment fonctionne insert() pour un ensemble? Comment l'ensemble vérifie-t-il si un élément existe déjà dedans?

J'ai lu sur wikipedia qu'une autre façon d'implémenter un ensemble est avec une table de hachage. Comment cela fonctionnerait-il?

46
zebraman

Vous pouvez implémenter une arborescence de recherche binaire en définissant d'abord une structure Node:

struct Node
{
  void *nodeData;
  Node *leftChild;
  Node *rightChild;
}

Ensuite, vous pouvez définir une racine de l'arbre avec un autre Node *rootNode;

L'entrée Wikipedia sur Arbre de recherche binaire a un assez bon exemple de la façon d'implémenter une méthode d'insertion, donc je recommanderais également de vérifier cela.

En termes de doublons, ils ne sont généralement pas autorisés dans les ensembles, vous pouvez donc simplement supprimer cette entrée, lever une exception, etc., en fonction de vos spécifications.

13
Raul Agrait

Comme l'a dit KTC, comment std::set est implémenté peut varier - la norme C++ spécifie simplement un type de données abstrait. En d'autres termes, la norme ne spécifie pas comment un conteneur doit être implémenté, mais quelles opérations il doit prendre en charge. Cependant, la plupart des implémentations de la STL utilisent, pour autant que je sache, arbres rouge-noir ou d'autres arbres de recherche binaires équilibrés d'une certaine sorte (GNU libstdc ++, par exemple, utilise des arbres rouge-noir) .

Bien que vous puissiez théoriquement implémenter un ensemble en tant que table de hachage et obtenir des performances asymptotiques plus rapides (O amorti (longueur de la clé) par rapport à O (log n) pour la recherche et l'insertion), cela nécessiterait que l'utilisateur fournisse une fonction de hachage pour le type qu'il souhaite pour stocker (voir entrée de Wikipedia sur les tables de hachage pour une bonne explication de leur fonctionnement). Quant à l'implémentation d'un arbre de recherche binaire, vous ne voudriez pas utiliser un tableau - comme Raul l'a mentionné, vous voudriez une sorte de structure de données Node.

23
Toli

Étape de débogage dans g++ 6.4 source stdlibc ++

Saviez-vous que sur le package Ubuntu 16.04 par défaut g++-6 Ou un version GCC 6.4 à partir de la source vous pouvez entrer dans la bibliothèque C++ sans autre configuration?

Ce faisant, nous concluons facilement qu'un arbre rouge-noir utilisé dans cette implémentation.

Cela a du sens, car std::set Peut être parcouru dans l'ordre, ce qui ne serait pas efficace si une carte de hachage était utilisée.

main.cpp

#include <cassert>
#include <set>

int main() {
    std::set<int> s;
    s.insert(1);
    s.insert(2);
    assert(s.find(1) != s.end());
    assert(s.find(2) != s.end());
    assert(s.find(3) == s3.end());
}

Compiler et déboguer:

g++ -g -std=c++11 -O0 -o main.out main.cpp
gdb -ex 'start' -q --args main.out

Maintenant, si vous entrez dans s.insert(1) vous atteignez immédiatement /usr/include/c++/6/bits/stl_set.h:

487 #if __cplusplus >= 201103L
488       std::pair<iterator, bool>
489       insert(value_type&& __x)
490       {
491     std::pair<typename _Rep_type::iterator, bool> __p =
492       _M_t._M_insert_unique(std::move(__x));
493     return std::pair<iterator, bool>(__p.first, __p.second);
494       }
495 #endif

qui transmet clairement à _M_t._M_insert_unique.

Nous ouvrons donc le fichier source dans vim et trouvons la définition de _M_t:

      typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
           key_compare, _Key_alloc_type> _Rep_type;
       _Rep_type _M_t;  // Red-black tree representing set.

Ainsi, _M_t Est de type _Rep_type Et _Rep_type Est un _Rb_tree.

OK, maintenant c'est assez de preuves pour moi. Si vous ne pensez pas que _Rb_tree Est un arbre noir-rouge, allez un peu plus loin et lisez l'algorithme.

unordered_set Utilise une table de hachage

Même procédure, mais remplacez set par unordered_set Sur le code.

Cela a du sens, car std::unordered_set Ne peut pas être parcouru dans l'ordre, donc la bibliothèque standard a choisi la carte de hachage au lieu de l'arbre rouge-noir, car la carte de hachage a une meilleure complexité de temps d'insertion amortie.

Entrer dans insert conduit à /usr/include/c++/6/bits/unordered_set.h:

415       std::pair<iterator, bool>
416       insert(value_type&& __x)
417       { return _M_h.insert(std::move(__x)); }

Nous ouvrons donc le fichier source dans vim et recherchons _M_h:

      typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;

C'est donc la table de hachage.

std::map Et std::unordered_map

Analogue pour std::set Vs std:unordered_set: Quelle structure de données est à l'intérieur de std :: map en C++?

Caractéristiques de performance

Vous pouvez également déduire la structure de données utilisée en les synchronisant:

enter image description here

Procédure de génération de graphique et analyse Heap vs BST et à: Heap vs Binary Search Tree (BST)

Nous voyons clairement pour:

  • std::set, Un temps d'insertion logarithmique
  • std::unordered_set, Un modèle de table de hachage de modèle plus complexe:

    • sur le graphique non zoomé, nous voyons clairement le tableau dynamique de support doubler sur un énorme hors des pics augmentant linéairement
    • sur le tracé zoomé, on voit que les temps sont fondamentalement constants et vont vers 250ns, donc beaucoup plus vite que le std::map, sauf pour les très petites tailles de carte

      Plusieurs bandes sont clairement visibles et leur inclinaison diminue lorsque le réseau double.

      Je crois que cela est dû à des promenades moyennes de listes chaînées augmentant linéairement avec chaque bac. Ensuite, lorsque le tableau double, nous avons plus de bacs, donc des marches plus courtes.

Je comprends que les ensembles STL sont basés sur la structure de données abstraite d'un arbre de recherche binaire. Quelle est donc la structure de données sous-jacente? Un tableau?

Comme d'autres l'ont souligné, cela varie. Un ensemble est généralement implémenté sous forme d'arbre (arbre rouge-noir, arbre équilibré, etc.) mais il peut exister d'autres implémentations.

En outre, comment insert () fonctionne-t-il pour un ensemble?

Cela dépend de l'implémentation sous-jacente de votre ensemble. S'il est implémenté comme un arbre binaire, Wikipedia a un exemple d'implémentation récursive pour la fonction insert (). Tu voudras peut-être vérifier.

Comment l'ensemble vérifie-t-il si un élément y existe déjà?

S'il est implémenté comme un arbre, il parcourt l'arbre et vérifie chaque élément. Cependant, les ensembles ne permettent pas de stocker les éléments en double. Si vous voulez un ensemble qui autorise les éléments en double, alors vous avez besoin d'un multi-ensemble.

J'ai lu sur wikipedia qu'une autre façon d'implémenter un ensemble est avec une table de hachage. Comment cela fonctionnerait-il?

Vous faites peut-être référence à un hash_set, où l'ensemble est implémenté à l'aide de tables de hachage. Vous devrez fournir une fonction de hachage pour savoir où stocker votre élément. Cette implémentation est idéale lorsque vous souhaitez pouvoir rechercher un élément rapidement. Cependant, s'il est important que vos éléments soient stockés dans un ordre particulier, l'implémentation de l'arborescence est plus appropriée car vous pouvez la parcourir en précommande, en ordre ou en post-commande.

8
jasonline

La façon dont un conteneur particulier est implémenté en C++ dépend entièrement de l'implémentation. Tout ce qui est requis est que le résultat réponde aux exigences définies dans la norme, telles que les exigences de complexité pour les différentes méthodes, les exigences des itérateurs, etc.

7
KTC

cppreference says :

Les ensembles sont généralement implémentés sous forme d'arbres rouge-noir.

J'ai vérifié, et les deux libc++ et libstdc++ utilise des arbres rouge-noir pour std::set.

std::unordered_set a été implémenté avec une table de hachage dans libc++ et je suppose la même chose pour libstdc++ mais n'a pas vérifié.

Edit: Apparemment, mon mot n'est pas assez bon.

  • libc++: 12
  • libstdc++: 1
1
Timmmm