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?
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.
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
.
É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:
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 logarithmiquestd::unordered_set
, Un modèle de table de hachage de modèle plus complexe:
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.
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.
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.