J'ai vu des gens dire que set
objets dans python ont O(1) vérification des membres. Comment sont-ils implémentés en interne pour permettre cela? Quelle sorte de structure de données utilise-t-il? Quelles autres implications cette implémentation at-elle?
Chaque réponse ici était vraiment éclairante, mais comme je ne peux en accepter qu’une, je vais donner la réponse la plus proche de ma question initiale. Merci à tous pour l'info!
Selon ce fil :
En effet, les ensembles de CPython sont implémentés comme des dictionnaires avec des valeurs factices (les clés étant les membres de l’ensemble), avec quelques optimisations exploitant ce manque de valeurs.
Donc, fondamentalement, set
utilise une table de hachage comme structure de données sous-jacente. Ceci explique la vérification de l'appartenance de O(1)), car rechercher un élément dans une table de hachage est une opération O(1), en moyenne.
Si vous le souhaitez, vous pouvez même parcourir le code source de CPython pour le set qui, selon Achim Domma , est principalement un copier-coller du dict
la mise en oeuvre.
Lorsque les gens disent que les ensembles ont O(1) vérification des membres, ils parlent du cas moyen . pire (lorsque toutes les valeurs hachées entrent en collision), la vérification de l'appartenance est O (n). Voir le wiki Python sur la complexité temporelle .
Le article Wikipédia indique la complexité temporelle pour une table de hachage qui ne redimensionne pas est O(1 + k/n)
. Ce résultat ne s'applique pas directement aux ensembles Python depuis Python utilisent une table de hachage qui est redimensionnée.
Un peu plus loin dans l'article de Wikipedia dit que pour le cas moyen , et en supposant une simple fonction de hachage uniforme, la complexité temporelle est O(1/(1-k/n))
, où k/n
peut être délimité par une constante c<1
.
Big-O se réfère uniquement au comportement asymptotique sous la forme n →. Puisque k/n peut être délimité par une constante, c <1, indépendant de n,
O(1/(1-k/n))
n'est pas plus grand que O(1/(1-c))
, ce qui équivaut à O(constant)
= O(1)
.
Donc, en supposant un hachage simple et uniforme, sur moyenne , la vérification de l'appartenance à Python définit est O(1)
.
Je pense que c'est une erreur courante, set
recherche (ou hashtable d'ailleurs) ne sont pas O (1).
de Wikipedia
Dans le modèle le plus simple, la fonction de hachage est complètement non spécifiée et le tableau ne redimensionne pas. Pour le meilleur choix possible de la fonction de hachage, une table de taille n avec adressage ouvert ne comporte pas de collision et peut contenir jusqu'à n éléments, avec une seule comparaison permettant une recherche réussie, et une table de taille n avec chaînage et k clés a le minimum minimum. (0, kn) collisions et comparaisons O (1 + k/n) pour la recherche. Pour le pire choix de fonction de hachage, chaque insertion provoque une collision et les tables de hachage dégénèrent en recherche linéaire, avec Ω (k) comparaisons amorties par insertion et jusqu'à k comparaisons pour une recherche réussie.
Nous avons tous un accès facile à la source , où le commentaire précédant set_lookkey()
dit:
/* set object implementation
Written and maintained by Raymond D. Hettinger <[email protected]>
Derived from Lib/sets.py and Objects/dictobject.c.
The basic lookup function used by all operations.
This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
The initial probe index is computed as hash mod the table size.
Subsequent probe indices are computed as explained in Objects/dictobject.c.
To improve cache locality, each probe inspects a series of consecutive
nearby entries before moving on to probes elsewhere in memory. This leaves
us with a hybrid of linear probing and open addressing. The linear probing
reduces the cost of hash collisions because consecutive memory accesses
tend to be much cheaper than scattered probes. After LINEAR_PROBES steps,
we then use open addressing with the upper bits from the hash value. This
helps break-up long chains of collisions.
All arithmetic on hash should ignore overflow.
Unlike the dictionary implementation, the lookkey function can return
NULL if the rich comparison returns an error.
*/
...
#ifndef LINEAR_PROBES
#define LINEAR_PROBES 9
#endif
/* This must be >= 1 */
#define PERTURB_SHIFT 5
static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)
{
...
Pour souligner un peu plus la différence entre set's
et dict's
, voici un extrait du setobject.c
sections de commentaires, qui clarifient la différence principale entre les ensembles et les dict.
Les cas d'utilisation des ensembles diffèrent considérablement des dictionnaires dans lesquels les clés recherchées sont plus susceptibles d'être présentes. En revanche, les ensembles concernent principalement les tests d’effectif lorsque la présence d’un élément n’est pas connue à l’avance. Par conséquent, la mise en œuvre définie doit être optimisée pour le cas trouvé et non trouvé.
source sur github