Dans les cas où j'ai une clé pour chaque élément et je ne connais pas l'index de l'élément dans un tableau, les tables de hachage fonctionnent mieux que les tableaux (O (1) vs O (n)).
Pourquoi donc? Je veux dire: j'ai une clé, je la hache .. j'ai la hache .. l'algorithme ne devrait-il pas comparer ce hachage avec le hachage de chaque élément? Je pense qu'il y a une astuce derrière la disposition de la mémoire, n'est-ce pas?
Dans les cas où j'ai une clé pour chaque élément et je ne connais pas l'index de l'élément dans un tableau, les tables de hachage fonctionnent mieux que les tableaux (O (1) vs O (n)).
La recherche de table de hachage effectue O(1) dans le cas moyen. Dans le pire des cas, la recherche de table de hachage effectue O (n): lorsque vous avez des collisions et que la fonction de hachage renvoie toujours la même chose On peut penser que "ceci est une situation éloignée", mais une bonne analyse doit en tenir compte. Dans ce cas, vous devez parcourir tous les éléments comme dans un tableau ou des listes liées (O (n)).
Pourquoi donc? Je veux dire: j'ai une clé, je la hache .. j'ai la hache .. l'algorithme ne devrait-il pas comparer ce hachage avec le hachage de chaque élément? Je pense qu'il y a une astuce derrière la disposition de la mémoire, n'est-ce pas?
Vous avez une clé, vous la hachez .. vous avez le hachage: l'index de la table de hachage où l'élément est présent (s'il a déjà été localisé). À ce stade, vous pouvez accéder à l'enregistrement de table de hachage dans O (1). Si le facteur de charge est petit, il est peu probable qu'il y ait plus d'un élément. Ainsi, le premier élément que vous voyez doit être l'élément que vous recherchez. Sinon, si vous avez plus d'un élément, vous devez comparer les éléments que vous trouverez dans la position avec l'élément que vous recherchez. Dans ce cas, vous avez O(1) + O (number_of_elements).
Dans le cas moyen, la complexité de recherche de la table de hachage est O(1) + O (load_factor) = O (1 + load_factor).
N'oubliez pas, load_factor = n dans le pire des cas. Ainsi, la complexité de la recherche est O(n) dans le pire des cas.
Je ne sais pas ce que vous voulez dire par "truc derrière la disposition de la mémoire". Selon certains points de vue, la table de hachage (avec sa structure et sa résolution de collisions par chaînage) peut être considérée comme une "astuce intelligente".
Bien sûr, les résultats de l'analyse de la table de hachage peuvent être prouvés par les mathématiques.
Avec des tableaux: si vous connaissez la valeur, vous devez rechercher en moyenne la moitié des valeurs (sauf tri) pour trouver son emplacement.
Avec hachages: l'emplacement est généré en fonction de la valeur. Donc, étant donné cette valeur à nouveau, vous pouvez calculer le même hachage que vous avez calculé lors de l'insertion. Parfois, plus d'une valeur entraîne le même hachage, donc en pratique chaque "emplacement" est lui-même un tablea (ou liste liée) de toutes les valeurs qui hachent à cet emplacement. Dans ce cas, seul ce tableau beaucoup plus petit (sauf s'il s'agit d'un mauvais hachage) doit être recherché.
Les tables de hachage sont un peu plus complexes. Ils placent les éléments dans différents seaux en fonction de leur valeur de hachage%. Dans une situation idéale, chaque seau contient très peu d'articles et il n'y a pas beaucoup de seaux vides.
Une fois que vous connaissez la clé, vous calculez le hachage. Sur la base du hachage, vous savez quel seau rechercher. Et comme indiqué ci-dessus, le nombre d'articles dans chaque seau doit être relativement faible.
Les tables de hachage font beaucoup de magie en interne pour s'assurer que les compartiments sont aussi petits que possible sans consommer trop de mémoire pour les compartiments vides. En outre, cela dépend beaucoup de la qualité de la clé -> fonction de hachage.
Wikipedia fournit description très complète de la table de hachage .
Une table de hachage n'aura pas à comparer tous les éléments du hachage. Il calculera le code de hachage en fonction de la clé. Par exemple, si la clé est 4, le code de hachage peut être - 4 * x * y. Maintenant, le pointeur sait exactement quel élément choisir.
Alors que s'il s'agit d'un tableau, il devra parcourir l'ensemble du tableau pour rechercher cet élément.
Pourquoi [est-il] que [les tables de hachage effectuent des recherches par clé mieux que les tableaux (O (1) vs O (n))]? Je veux dire: j'ai une clé, je la hache .. j'ai la hache .. l'algorithme ne devrait-il pas comparer ce hachage avec le hachage de chaque élément? Je pense qu'il y a une astuce derrière la disposition de la mémoire, n'est-ce pas?
Une fois que vous avez le hachage, il vous permet de calculer un emplacement "idéal" ou prévu dans le tableau de compartiments: généralement:
seau idéal = hachage% num_buckets
Le problème est alors qu'une autre valeur peut déjà avoir été hachée dans ce compartiment, auquel cas l'implémentation de la table de hachage a deux choix principaux:
1) essayez un autre seau
2) laisser plusieurs valeurs distinctes "appartenir" à un seul compartiment, peut-être en faisant en sorte que le compartiment contienne un pointeur dans une liste de valeurs liées
Pour l'implémentation 1, connue sous le nom de adressage ouvert ou hachage fermé , vous sautez autour d'autres seaux: si vous trouvez votre valeur, super; si vous trouvez un compartiment jamais utilisé, vous pouvez y stocker votre valeur si vous l'insérez, ou vous savez que vous ne trouverez jamais votre valeur lors de la recherche. Il est possible que la recherche soit encore pire que O(n) si la façon dont vous parcourez les autres compartiments finit par rechercher le même compartiment plusieurs fois; par exemple, si vous utilisez sondage quadratique vous essayez l'indice de godet idéal +1, puis +4, puis +9, puis +16 et ainsi de suite - mais vous devez éviter l'accès au compartiment hors limites en utilisant par exemple % num_buckets
, donc s'il y a par exemple 12 compartiments, alors idéal + 4 et idéal + 16 recherchent le même compartiment. Il peut être coûteux de suivre les compartiments qui ont été recherchés, il peut donc être difficile de savoir quand abandonner également: la mise en œuvre peut être optimiste et supposer qu'elle trouvera toujours la valeur ou un compartiment inutilisé (risquant de tourner pour toujours), elle peut avoir un compteur et, après un certain nombre d'essais, abandonner ou lancer une recherche linéaire compartiment par compartiment.
Pour l'implémentation 2, connue sous le nom de adressage fermé ou chaînage séparé , vous devez rechercher dans le conteneur/structure de données des valeurs qui ont toutes été hachées dans le compartiment idéal. Son efficacité dépend du type de conteneur utilisé. On s'attend généralement à ce que le nombre d'éléments entrant en collision dans un compartiment soit petit, ce qui est vrai d'une bonne fonction de hachage avec des entrées non contradictoires, et généralement assez vrai, même d'une fonction de hachage médiocre, en particulier avec un nombre premier de compartiments. Ainsi, une liste liée ou un tableau contigu est souvent utilisé, malgré les propriétés de recherche O(n): les listes liées sont simples à implémenter et à utiliser, et les tableaux regroupent les données pour un meilleur cache mémoire la localité et la vitesse d'accès. Le pire des cas est que chaque valeur de votre table est hachée dans le même compartiment, et que le conteneur de ce compartiment contient maintenant toutes les valeurs: votre table de hachage entière n'est alors aussi efficace que le conteneur du compartiment. Certaines implémentations de table de hachage Java Java ont commencé à utiliser des arbres binaires si le nombre d'éléments hachés vers les mêmes compartiments dépasse un seuil, pour s'assurer que la complexité n'est jamais pire que O (log2n).
Les hachages Python sont un exemple de 1 = adressage ouvert = hachage fermé. C++ std::unordered_set
est un exemple d'adressage fermé = chaînage séparé.