web-dev-qa-db-fra.com

Comment fonctionne une table de hachage?

Je cherche une explication sur le fonctionnement d'une table de hachage - en anglais simple pour un simple comme moi!

Par exemple, je sais que cela prend la clé, calcule le hachage (je cherche une explication comment) puis effectue une sorte de modulo pour déterminer où il se trouve dans le tableau où la valeur est stockée, mais c’est là que mes connaissances s’arrêtent. .

Quelqu'un pourrait-il clarifier le processus?

Edit: Je ne vous demande pas précisément comment les codes de hachage sont calculés, mais un aperçu général du fonctionnement d'une table de hachage.

467
Arec Barrwin

Voici une explication en termes simples.

Supposons que vous souhaitiez remplir une bibliothèque de livres et pas seulement y ranger, mais que vous souhaitiez pouvoir les retrouver facilement lorsque vous en avez besoin.

Donc, vous décidez que si la personne qui veut lire un livre connaît le titre du livre et le titre exact pour démarrer, c'est tout ce que cela devrait prendre. Avec le titre, la personne, avec l'aide du bibliothécaire, devrait pouvoir trouver le livre facilement et rapidement.

Alors, comment pouvez-vous faire ça? Bien évidemment, vous pouvez conserver une sorte de liste de l'emplacement de chaque livre, mais vous avez le même problème que lorsque vous effectuez une recherche dans la bibliothèque, vous devez effectuer une recherche dans la liste. Certes, la liste serait plus petite et plus facile à rechercher, mais vous ne voudriez tout de même pas chercher séquentiellement d'un bout à l'autre de la bibliothèque (ou de la liste).

Vous voulez quelque chose qui, avec le titre du livre, puisse vous donner le bon endroit à la fois, il vous suffit donc de vous promener sur la bonne étagère et de prendre le livre.

Mais comment cela peut-il être fait? Eh bien, avec un peu de prévoyance lorsque vous remplissez la bibliothèque et beaucoup de travail lorsque vous remplissez la bibliothèque.

Au lieu de simplement commencer à remplir la bibliothèque d'un bout à l'autre, vous concevez une petite méthode intelligente. Vous prenez le titre du livre, vous le lancez dans un petit programme informatique, qui crache un numéro d'étagère et un numéro d'emplacement sur cette étagère. C'est ici que vous placez le livre.

La beauté de ce programme réside dans le fait que plus tard, quand une personne revient lire le livre, vous insérez le titre dans le programme une fois de plus et récupérez le même numéro de tablette et le même numéro d’emplacement que ceux qui vous ont été attribués à l’origine. où se trouve le livre.

Le programme, comme d'autres l'ont déjà mentionné, s'appelle un algorithme de hachage ou un calcul de hachage et fonctionne généralement en prenant les données qui y sont introduites (le titre du livre dans ce cas) et en calcule un nombre.

Pour simplifier, supposons que chaque lettre et chaque symbole soient convertis en chiffres et résumés. En réalité, c'est beaucoup plus compliqué que cela, mais restons-en là pour le moment.

La beauté d'un tel algorithme réside dans le fait que si vous introduisez la même entrée encore et encore, il continuera à cracher le même nombre à chaque fois.

Ok, c'est comme ça une table de hachage.

Des trucs techniques suivent.

Premièrement, il y a la taille du nombre. Habituellement, la sortie d'un tel algorithme de hachage se situe dans une plage d'un grand nombre, généralement beaucoup plus grande que l'espace disponible dans votre tableau. Par exemple, supposons que nous ayons de la place pour exactement un million de livres dans la bibliothèque. Le résultat du calcul du hachage pourrait être compris entre 0 et un milliard, ce qui est beaucoup plus élevé.

Alors que faisons-nous? Nous utilisons quelque chose appelé calcul de module, qui dit en gros que si vous comptez le nombre que vous voulez (c’est-à-dire le milliard) mais que vous voulez rester dans une plage beaucoup plus petite, chaque fois que vous atteignez la limite de cette plage plus petite, vous êtes parti 0, mais vous devez savoir jusqu'où vous êtes dans la grande séquence.

Supposons que la sortie de l'algorithme de hachage se situe dans la plage de 0 à 20 et que vous obtenez la valeur 17 d'un titre particulier. Si la bibliothèque ne compte que 7 livres, vous comptez 1, 2, 3, 4, 5, 6 et quand vous arrivez à 7, vous recommencez à 0. Comme nous devons compter 17 fois, nous avons 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3 et le nombre final est 3.

Bien sûr, le calcul du module n'est pas fait comme ça, mais avec division et reste. Le reste de la division de 17 par 7 est 3 (7 va 2 fois en 17 à 14 et la différence entre 17 et 14 est 3).

Ainsi, vous mettez le livre dans l'emplacement numéro 3.

Cela conduit au problème suivant. Collisions Comme l'algorithme n'a aucun moyen d'espacer les livres pour qu'ils remplissent exactement la bibliothèque (ou la table de hachage si vous voulez), il finira toujours par calculer un nombre déjà utilisé. Dans le sens de la bibliothèque, quand vous arrivez sur l'étagère et le numéro d'emplacement dans lequel vous souhaitez mettre un livre, il y a déjà un livre.

Il existe différentes méthodes de traitement des collisions, notamment l’exécution des données dans un autre calcul pour obtenir un autre emplacement dans la table ( double hachage ) ou simplement pour trouver un espace proche de celui qui vous a été attribué (c’est-à-dire juste à côté). dans le livre précédent en supposant que la fente était disponible, également appelé sondage linéaire ). Cela voudrait dire que vous aurez du mal à faire des recherches lorsque vous essaierez de trouver le livre plus tard, mais cela reste quand même mieux que de commencer simplement à une extrémité de la bibliothèque.

Enfin, vous voudrez peut-être mettre plus de livres dans la bibliothèque que ne le permet la bibliothèque. En d'autres termes, vous devez construire une bibliothèque plus grande. Étant donné que l'emplacement exact dans la bibliothèque a été calculé à l'aide de la taille exacte et actuelle de la bibliothèque, il va sans dire que si vous redimensionnez la bibliothèque, vous risquez de devoir trouver de nouveaux emplacements pour tous les livres depuis le calcul effectué pour trouver leurs emplacements. a changé.

J'espère que cette explication était un peu plus terre à terre que les seaux et les fonctions :)

Usage et Lingo:

  1. Tables de hachage sont utilisés pour stocker et récupérer rapidement des données (ou des enregistrements).
  2. Les enregistrements sont stockés dans des seaux en utilisant touches de hachage
  3. Touches de hachage sont calculés en appliquant un algorithme de hachage à une valeur choisie (le clé valeur) contenus dans l'enregistrement. Cette valeur choisie doit être une valeur commune à tous les enregistrements.
  4. Chaque seau peut avoir plusieurs enregistrements organisés dans un ordre particulier.

Exemple du monde réel:

Hash & Co., fondée en 1803 et dépourvue de toute technologie informatique, disposait d’un total de 300 classeurs pour conserver les informations détaillées (les archives) de leurs quelque 30 000 clients. Chaque dossier a été clairement identifié avec son numéro de client, un numéro unique compris entre 0 et 29 999.

Les commis-archivistes de cette époque devaient rapidement récupérer et stocker les dossiers des clients pour le personnel en activité. Le personnel avait décidé qu'il serait plus efficace d'utiliser une méthode de hachage pour stocker et récupérer leurs enregistrements.

Pour classer une fiche client, les archivistes utilisent le numéro de client unique inscrit dans le dossier. En utilisant ce numéro de client, ils moduleraient le touche dièse par 300 pour identifier le classeur dans lequel il est contenu. Lorsqu’ils ouvriront le classeur, ils découvriront qu’il contient de nombreux dossiers ordonnés par numéro de client. Après avoir identifié le bon emplacement, ils l'inséreraient tout simplement.

Pour récupérer un dossier client, un classeur se voit attribuer un numéro de client sur un bout de papier. En utilisant ce numéro de client unique (le touche dièse), ils le moduleraient par 300 pour déterminer quel classeur contenait le dossier des clients. Lorsqu'ils ouvriront le classeur, ils découvriraient qu'il contenait de nombreux dossiers classés par numéro de client. En parcourant les enregistrements, ils trouveraient rapidement le dossier client et le récupéreraient.

Dans notre exemple du monde réel, notre des seaux sont classeurs et notre enregistrements sont dossiers de fichiers.


Il est important de se rappeler que les ordinateurs (et leurs algorithmes) gèrent mieux les nombres que les chaînes. Ainsi, accéder à un grand tableau à l'aide d'un index est beaucoup plus rapide que d'accéder séquentiellement.

Comme Simon a mentionné que je crois être très important est que la partie de hachage consiste à transformer un grand espace (de longueur arbitraire, généralement des chaînes de caractères, etc.) et à le mapper sur un petit espace (de taille connue, généralement des nombres) pour l'indexation. Ceci est très important à retenir!

Ainsi, dans l'exemple ci-dessus, les 30 000 clients possibles ou plus sont mappés vers un espace plus petit.


L’idée principale est de diviser l’ensemble du jeu de données en segments afin d’accélérer les recherches, ce qui prend généralement beaucoup de temps. Dans notre exemple ci-dessus, chacun des 300 classeurs contiendrait (statistiquement) environ 100 enregistrements. La recherche (quel que soit l'ordre) sur 100 enregistrements est beaucoup plus rapide que de traiter 30 000 enregistrements.

Vous avez peut-être remarqué que certains le font déjà. Mais au lieu de concevoir une méthodologie de hachage pour générer une clé de hachage, ils utiliseront dans la plupart des cas simplement la première lettre du nom de famille. Donc, si vous avez 26 classeurs contenant chacun une lettre de A à Z, vous n’avez en théorie que segmenté vos données et amélioré le processus de classement et de récupération.

J'espère que cela t'aides,

Jeach!

95
Jeach

Cela s'avère être un domaine assez profond de la théorie, mais le contour de base est simple.

Essentiellement, une fonction de hachage est simplement une fonction qui prend les choses d'un espace (par exemple des chaînes de longueur arbitraire) et les mappe dans un espace utile pour l'indexation (entiers non signés, par exemple).

Si vous avez seulement un petit espace d'éléments à hacher, vous pouvez vous contenter d'interpréter ces éléments sous forme d'entiers et vous avez terminé (par exemple, une chaîne de 4 octets).

Habituellement, vous disposez d'un espace beaucoup plus grand. Si l'espace des éléments que vous autorisez en tant que clés est supérieur à celui des éléments que vous utilisez pour indexer (ceux de votre uint32 ou quoi que ce soit d'autre), vous ne pouvez pas avoir une valeur unique pour chaque élément. Lorsque deux éléments ou plus donnent le même résultat, vous devez gérer la redondance de manière appropriée (on parle généralement de collision et la façon dont vous gérez cela ou non dépendra un peu de ce que vous êtes. en utilisant le hachage pour).

Cela signifie que vous voulez qu'il soit peu probable que le même résultat se produise et que vous souhaitiez aussi vraiment que la fonction de hachage soit rapide.

Équilibrer ces deux propriétés (et quelques autres) a occupé beaucoup de monde!

En pratique, vous devriez généralement pouvoir trouver une fonction qui fonctionne bien pour votre application et l'utiliser.

Maintenant, pour que cela fonctionne comme une table de hachage: Imaginez que vous ne vous souciez pas de l’utilisation de la mémoire. Ensuite, vous pouvez créer un tableau tant que votre ensemble d'indexation (tous les uint32, par exemple). Lorsque vous ajoutez quelque chose à la table, vous hachez sa clé et regardez le tableau à cet index. S'il n'y a rien là-bas, vous mettez votre valeur là-bas. S'il y a déjà quelque chose là-bas, vous ajoutez cette nouvelle entrée à une liste d'éléments à cette adresse, avec suffisamment d'informations (votre clé d'origine ou quelque chose d'intelligent) pour trouver quelle entrée appartient réellement à quelle clé.

Ainsi, au fur et à mesure que vous avancez, chaque entrée de votre hashtable (le tableau) est soit vide, soit contient une entrée, ou une liste d'entrées. La récupération consiste simplement à indexer dans le tableau et à renvoyer la valeur, ou à parcourir la liste de valeurs et à renvoyer la bonne.

Bien sûr, dans la pratique, vous ne pouvez généralement pas faire cela, cela gaspille trop de mémoire. Donc, vous faites tout en vous basant sur un tableau fragmenté (où les seules entrées sont celles que vous utilisez réellement, tout le reste est implicitement nul).

Il existe de nombreux stratagèmes et astuces pour améliorer le fonctionnement de ce logiciel, mais ce sont les principes de base.

64
simon

Beaucoup de réponses, mais aucune d'entre elles n'est très visuel, et les tables de hachage peuvent facilement "cliquer" lorsqu'elles sont visualisées.

Les tables de hachage sont souvent implémentées sous forme de tableaux de listes chaînées. Si nous imaginons une table contenant les noms des personnes, après quelques insertions, elle pourrait être mise en mémoire, comme ci-dessous, où ()- les nombres inclus sont des valeurs de hachage du texte/nom.

bucket#  bucket content / linked list

[0]      --> "sue"(780) --> null
[1]      null
[2]      --> "fred"(42) --> "bill"(9282) --> "jane"(42) --> null
[3]      --> "mary"(73) --> null
[4]      null
[5]      --> "masayuki"(75) --> "sarwar"(105) --> null
[6]      --> "margaret"(2626) --> null
[7]      null
[8]      --> "bob"(308) --> null
[9]      null

Quelques points:

  • chacune des entrées du tableau (indices [0], [1]...) est appelée compartiment ​​, et commence une liste éventuellement liée de valeurs (aussi éléments, dans cet exemple - les gens noms)
  • chaque valeur (par exemple "fred" avec un hachage 42) est liée à partir du compartiment [hash % number_of_buckets] par exemple. 42 % 10 == [2]; % est le opérateur [modulo - le reste divisé par le nombre de compartiments
  • plusieurs valeurs de données peuvent collision à et être liées à partir du même compartiment, le plus souvent parce que leurs valeurs de hachage se heurtent après l'opération modulo (par exemple, 42 % 10 == [2] et 9282 % 10 == [2]), mais occasionnellement parce que les valeurs de hachage sont identiques (par exemple, "fred" et "jane" apparaissant avec un hachage 42 ci-dessus)
    • la plupart des tables de hachage gèrent les collisions - avec des performances légèrement réduites mais sans confusion fonctionnelle - en comparant la valeur complète (ici du texte) d'une valeur recherchée ou insérée dans chaque valeur déjà présente dans la liste liée dans le compartiment haché

La longueur des listes chaînées est liée au facteur de charge et non au nombre de valeurs

Si la taille de la table augmente, les tables de hachage implémentées ci-dessus ont tendance à se redimensionner (c.-à-d. Créer un plus grand tableau de compartiments, créer des listes chaînées nouvelles/mises à jour à partir de celle-ci, supprimer l'ancien tableau) pour conserver le rapport des valeurs aux compartiments (aka Facteur de charge) quelque part dans la plage de 0,5 à 1,0.

Hans donne la formule réelle pour les autres facteurs de charge dans un commentaire ci-dessous, mais pour les valeurs indicatives: avec un facteur de charge 1 et une fonction de hachage de la force cryptographique, 1/e (~ 36,8%) des compartiments aura tendance à être vide, un autre 1/e (~ 36,8%) ont un élément, 1/(2e) ou ~ 18,4% deux éléments, 1/(3! E) environ 6,1% trois éléments, 1/(4! E) ou ~ 1,5% quatre éléments, 1/(5! E) ~ .3% en ont cinq, etc. - la longueur moyenne d'une chaîne de seaux non vides est d'environ 1,58, quel que soit le nombre d'éléments figurant dans le tableau (c'est-à-dire s'il y a 100 éléments et 100 seaux, ou 100 millions C'est pourquoi nous appelons lookup/insert/erase O (1) opérations à temps constant.

Comment une table de hachage peut associer des clés à des valeurs

Avec une implémentation de table de hachage telle que décrite ci-dessus, nous pouvons imaginer créer un type de valeur tel que struct Value { string name; int age; };, ainsi que des fonctions de comparaison d'égalité et de hachage qui ne concernent que le champ name (en ignorant l'âge), puis quelque chose de merveilleux. arrive: on peut stocker Value enregistrements comme {"sue", 63} dans la table, puis rechercher "poursuivre" sans connaître son âge, trouver la valeur stockée et récupérer ou même mettre à jour son âge.
- Joyeux anniversaire Sue - ce qui est intéressant ne change pas la valeur de hachage et ne nécessite donc pas de déplacer le disque de Sue dans un autre seau.

Lorsque nous faisons cela, nous utilisons la table de hachage en tant que [conteneur associatif également carte , et les valeurs qu'elle stocke peuvent être considérées comme étant constituées. d'un clé (le nom) et d'un ou plusieurs autres champs encore nommés - ce qui prête à confusion - le valeur (dans mon exemple, seulement l'âge). Une implémentation de table de hachage utilisée en tant que carte est appelée hash map.

Cela contraste avec l'exemple précédent dans cette réponse où nous avons stocké des valeurs discrètes telles que "sue", que vous pourriez considérer comme étant sa propre clé: ce type d'utilisation est appelé hash set.

Il existe d'autres moyens d'implémenter une table de hachage

Toutes les tables de hachage n'utilisent pas de listes chaînées (appelé --- [chaînage séparé ) ==), mais la plupart des applications générales le font, comme alternative principale hash fermé (alias adressage ouvert) - en particulier avec les opérations d'effacement prises en charge - a des propriétés de performance moins stables avec des clés/fonctions de hachage sujettes aux collisions.


Quelques mots sur les fonctions de hachage

Fort hachage ...

La fonction de hachage minimisant les collisions, dans le pire des cas, a pour objectif général de pulvériser efficacement les clés autour des compartiments de table de hachage de manière efficace et aléatoire, tout en générant toujours la même valeur de hachage pour la même clé. Même un bit qui change n'importe où dans la clé devrait idéalement - de manière aléatoire - retourner environ la moitié des bits de la valeur de hachage résultante.

Ceci est normalement orchestré avec des maths trop compliquées pour que je puisse en parler. Je vais mentionner un moyen facile à comprendre - pas le plus évolutif ni le plus convivial pour le cache, mais intrinsèquement élégant (comme le cryptage avec un tampon ponctuel!) - car je pense que cela aide à faire comprendre les qualités souhaitables mentionnées ci-dessus. Disons que vous êtes en train de hacher doubles 64 bits - vous pouvez créer 8 tables de 256 nombres aléatoires (code ci-dessous), puis utiliser chaque tranche de 8 bits/1 octet de la représentation en mémoire de double pour indexer dans une autre table, XORing les nombres aléatoires que vous recherchez. Avec cette approche, il est facile de voir qu'un bit (au sens des chiffres binaires) qui change n'importe où dans la variable double donne lieu à la recherche d'un nombre aléatoire différent dans l'un des tableaux et à une valeur finale totalement non corrélée.

// note caveats above: cache unfriendly (SLOW) but strong hashing...
size_t random[8][256] = { ...random data... };
const char* p = (const char*)&my_double;
size_t hash = random[0][p[0]] ^ random[1][p[1]] ^ ... ^ random[7][p[7]];

Hachage faible mais souvent rapide ...

Les fonctions de hachage de nombreuses bibliothèques transmettent des nombres entiers de manière inchangée (appelée fonction trivial ou identity ) c'est l'extrême extrême du hachage fort décrit ci-dessus. Un hachage d’identité est extrêmement ​​prédisposé aux collisions dans les cas les plus graves, mais l’espoir est que, dans le cas assez courant de clés entières qui tendent à s’incrémenter (peut-être avec quelques lacunes), elles se traceront en séquences successives. les compartiments laissent moins de feuilles vides que de feuilles de hachage aléatoires (notre ~ 36,8% au facteur de charge 1 mentionné précédemment), ce qui entraîne moins de collisions et moins de listes chaînées d'éléments en collision plus longues que celles obtenues par les correspondances aléatoires. Il est également intéressant d’économiser le temps nécessaire pour générer un hachage fort. Si les clés sont recherchées dans l’ordre, elles seront placées dans des compartiments situés dans la mémoire, améliorant ainsi les résultats en cache. Lorsque les touches notez pas incrémentent bien, on espère qu'elles seront suffisamment aléatoires pour ne pas avoir besoin d'une fonction de hachage puissante pour randomiser totalement leur placement dans des compartiments.

42
Tony Delroy

Vous êtes sur le point d'expliquer cela de près, mais il vous manque quelques éléments. La table de hachage est juste un tableau. Le tableau lui-même contiendra quelque chose dans chaque emplacement. Au minimum, vous stockerez la valeur de hachage ou la valeur elle-même dans cet emplacement. En plus de cela, vous pouvez également stocker une liste chaînée/chaînée de valeurs qui sont entrées en collision sur cet emplacement, ou vous pouvez utiliser la méthode d'adressage ouvert. Vous pouvez également stocker un ou plusieurs pointeurs sur d'autres données que vous souhaitez extraire de cet emplacement.

Il est important de noter que la valeur de hachage elle-même n'indique généralement pas l'emplacement dans lequel placer la valeur. Par exemple, une valeur de hachage peut être une valeur entière négative. De toute évidence, un nombre négatif ne peut pas pointer vers un emplacement de tableau. De plus, les valeurs de hachage auront souvent tendance à être des nombres plus grands que les emplacements disponibles. Ainsi, un autre calcul doit être effectué par la table de hachage elle-même pour déterminer dans quel emplacement la valeur doit être placée. Ceci est fait avec une opération de module mathématique comme:

uint slotIndex = hashValue % hashTableSize;

Cette valeur est l'emplacement dans lequel la valeur ira. En adressage ouvert, si l'emplacement est déjà rempli avec une autre valeur de hachage et/ou d'autres données, l'opération de module sera exécutée une nouvelle fois pour trouver le prochain emplacement:

slotIndex = (remainder + 1) % hashTableSize;

Je suppose qu’il existe peut-être d’autres méthodes plus avancées pour déterminer l’indice de créneaux horaires, mais c’est la méthode la plus courante que j’ai vue… qui intéresserait d’autres qui fonctionnent mieux.

Avec la méthode du module, si vous avez une table de taille 1000, toute valeur de hachage comprise entre 1 et 1000 ira dans le logement correspondant. Toute valeur négative et toute valeur supérieure à 1000 seront des valeurs de créneau potentiellement en collision. Les chances que cela se produise dépendent à la fois de votre méthode de hachage et du nombre total d'éléments ajoutés à la table de hachage. En règle générale, il est recommandé de définir la taille de la table de hachage de telle sorte que le nombre total de valeurs qui y sont ajoutées ne représente que 70% environ de sa taille. Si votre fonction de hachage fait un bon travail de distribution uniforme, vous ne rencontrerez généralement que très peu, voire aucune, collisions de compartiment/emplacement et elle s'exécutera très rapidement pour les opérations de recherche et d'écriture. Si le nombre total de valeurs à ajouter n'est pas connu à l'avance, faites une bonne estimation en utilisant n'importe quel moyen, puis redimensionnez votre hashtable une fois que le nombre d'éléments ajoutés a atteint 70% de sa capacité.

J'espère que cela a aidé.

PS - En C #, la méthode GetHashCode() est assez lente et entraîne des collisions de valeurs réelles dans de nombreuses conditions que j'ai testées. Pour vous amuser vraiment, créez votre propre fonction de hachage et essayez de ne jamais la mettre en collision avec les données spécifiques que vous hachez, courez plus vite que GetHashCode et disposez d'une distribution à peu près égale. J'ai utilisé cette méthode en utilisant des valeurs de hashcode longues au lieu de valeurs int et cela fonctionnait assez bien avec jusqu'à 32 millions d'entrées de valeurs de hachage dans la table de hachage avec 0 collision. Malheureusement, je ne peux pas partager le code car il appartient à mon employeur ... mais je peux révéler qu'il est possible pour certains domaines de données. Lorsque vous pouvez y parvenir, la table de hachage est TRÈS rapide. :)

24
Chris

Voici comment cela fonctionne dans ma compréhension:

Voici un exemple: imaginez le tableau entier comme une série de seaux. Supposons que vous ayez une implémentation avec des codes de hachage alphanumériques et que vous disposiez d'un compartiment pour chaque lettre de l'alphabet. Cette implémentation place chaque élément dont le code de hachage commence par une lettre particulière dans le compartiment correspondant.

Supposons que vous avez 200 objets, mais que seulement 15 d’entre eux ont des codes de hachage commençant par la lettre "B". La table de hachage aurait seulement besoin de rechercher et de parcourir les 15 objets du compartiment "B", plutôt que les 200 objets.

En ce qui concerne le calcul du code de hachage, il n’ya rien de magique. L'objectif est simplement de faire en sorte que différents objets renvoient des codes différents et pour que des objets égaux renvoient des codes égaux. Vous pouvez écrire une classe qui retourne toujours le même entier qu'un code de hachage pour toutes les instances, mais vous détruisez essentiellement l'utilité d'une table de hachage, qui deviendrait simplement un seau géant.

17
AndreiM

Court et doux:

Une table de hachage enveloppe un tableau, appelons-le internalArray. Les éléments sont insérés dans le tableau de la manière suivante:

let insert key value =
    internalArray[hash(key) % internalArray.Length] <- (key, value)
    //oversimplified for educational purposes

Parfois, deux clés vont avoir le même index dans le tableau et vous voulez conserver les deux valeurs. J'aime stocker les deux valeurs dans le même index, ce qui est simple à coder en faisant internalArray un tableau de listes chaînées:

let insert key value =
    internalArray[hash(key) % internalArray.Length].AddLast(key, value)

Donc, si je voulais récupérer un élément de ma table de hachage, je pourrais écrire:

let get key =
    let linkedList = internalArray[hash(key) % internalArray.Length]
    for (testKey, value) in linkedList
        if (testKey = key) then return value
    return null

Les opérations de suppression sont aussi simples à écrire. Comme vous pouvez le constater, les insertions, les recherches et le retrait de notre tableau de listes chaînées sont presque O (1).

Lorsque notre tableau interneArray devient trop complet, avec une capacité d'environ 85%, nous pouvons redimensionner le tableau interne et déplacer tous les éléments de l'ancien tableau vers le nouveau.

13
Juliet

C'est encore plus simple que ça.

Une table de hachage n'est rien d'autre qu'un tableau (généralement peu dense un) de vecteurs contenant des paires clé/valeur. La taille maximale de ce tableau est généralement inférieure au nombre d'éléments de l'ensemble de valeurs possibles pour le type de données stockées dans la table de hachage.

L'algorithme de hachage est utilisé pour générer un index dans ce tableau en fonction des valeurs de l'élément qui sera stocké dans le tableau.

C’est là que l’entreposage des vecteurs de paires clé/valeur dans le tableau entre en jeu. Comme l’ensemble des valeurs pouvant être des index dans le tableau est généralement inférieur au nombre de toutes les valeurs possibles que le type peut avoir, il est possible que votre hachage algorithme va générer la même valeur pour deux clés séparées. Un bon algorithme de hachage évitera cela autant que possible (raison pour laquelle il est relégué au type en général, car il contient des informations spécifiques, telles qu'un algorithme de hachage général ne peut pas savoir), mais il est impossible d'empêcher.

De ce fait, vous pouvez avoir plusieurs clés générant le même code de hachage. Lorsque cela se produit, les éléments du vecteur sont itérés et une comparaison directe est effectuée entre la clé du vecteur et la clé recherchée. Si cette valeur est trouvée, la valeur associée à la clé est renvoyée, sinon rien n'est renvoyé.

10
casperOne

Vous prenez un tas de choses et un tableau.

Pour chaque chose, vous créez un index, appelé hash. L'important dans le hash est qu'il se "disperse" beaucoup; vous ne voulez pas que deux choses similaires aient des hachages similaires.

Vous placez vos objets dans le tableau à la position indiquée par le hachage. Plus d'une chose peut aboutir à un hachage donné, alors vous stockez les choses dans des tableaux ou quelque chose d'autre approprié, que nous appelons généralement un seau.

Lorsque vous recherchez des éléments dans le hachage, vous suivez les mêmes étapes pour déterminer la valeur de hachage, puis voir ce qu'il y a dans le seau à cet endroit et vérifier si c'est ce que vous recherchez.

Lorsque votre hachage fonctionne bien et que votre tableau est assez grand, il ne restera que quelques éléments au maximum dans un index particulier du tableau, vous n'aurez donc pas à regarder beaucoup plus loin.

Pour les points bonus, assurez-vous que lorsque vous accédez à votre table de hachage, la chose trouvée (le cas échéant) se déplace vers le début du seau. La prochaine fois, c'est la première chose vérifiée.

9
chaos

Voici une autre façon de voir les choses.

Je suppose que vous comprenez le concept d'un tableau A. C'est quelque chose qui prend en charge l'opération d'indexation, où vous pouvez accéder à l'élément Ith, A [I], en une étape, quelle que soit la taille de A.

Ainsi, par exemple, si vous souhaitez stocker des informations sur un groupe de personnes ayant toutes des âges différents, un moyen simple serait de disposer d'un tableau suffisamment grand et d'utiliser l'âge de chaque personne comme index dans le tableau. De cette manière, vous pourriez avoir un accès en une étape aux informations de toute personne.

Mais bien sûr, il pourrait y avoir plus d'une personne du même âge. Par conséquent, ce que vous mettez dans le tableau à chaque entrée est une liste de toutes les personnes qui ont cet âge. Ainsi, vous pouvez accéder aux informations d'une personne en une seule étape, plus un petit peu de recherche dans cette liste (appelée "seau"). Cela ne ralentit que s'il y a tellement de monde que les paniers deviennent grands. Ensuite, vous avez besoin d'un tableau plus large et d'un autre moyen d'obtenir davantage d'informations d'identification sur la personne, telles que les premières lettres de son nom de famille, au lieu d'utiliser l'âge.

C'est l'idée de base. Au lieu d'utiliser l'âge, toute fonction de la personne qui produit une bonne diffusion de valeurs peut être utilisée. C'est la fonction de hachage. Comme si vous pouviez prendre un tiers de la représentation ASCII du nom de la personne, brouillée dans un ordre quelconque. Tout ce qui compte, c'est que vous ne souhaitiez pas que trop de personnes utilisent le même seau, car la vitesse dépend du fait que les seaux restent petits.

3
Mike Dunlavey

Jusqu'à présent, toutes les réponses sont bonnes et abordent différents aspects du fonctionnement d'une table de hachage. Voici un exemple simple qui pourrait être utile. Disons que nous voulons stocker des éléments avec des chaînes alphabétiques en minuscules comme clés.

Comme l'explique Simon, la fonction de hachage est utilisée pour mapper un grand espace sur un petit espace. Une implémentation simple et naïve d'une fonction de hachage pour notre exemple pourrait prendre la première lettre de la chaîne et la mapper sur un entier, ainsi "alligator" a un code de hachage de 0, "bee" a un code de hachage de 1, " zèbre "serait 25, etc.

Ensuite, nous avons un tableau de 26 compartiments (pouvant être ArrayLists en Java), et nous plaçons l'élément dans le compartiment qui correspond au code de hachage de notre clé. Si nous avons plusieurs éléments dont la clé commence par la même lettre, ils auront le même code de hachage. Tous les éléments iront dans le compartiment pour ce code de hachage, de sorte qu'une recherche linéaire doit être effectuée dans le compartiment pour trouver un article particulier.

Dans notre exemple, si nous avions seulement quelques douzaines d’articles avec des touches couvrant l’alphabet, cela fonctionnerait très bien. Cependant, si nous avions un million d'éléments ou toutes les clés commencées par 'a' ou 'b', notre table de hachage ne serait pas idéale. Pour obtenir de meilleures performances, nous aurions besoin d'une fonction de hachage différente et/ou de plusieurs compartiments.

3
Greg Graham

Le calcul du hachage ne dépend généralement pas de la hashtable mais des éléments qui y sont ajoutés. Dans les bibliothèques de classes frameworks/base telles que .net et Java, chaque objet a une méthode GetHashCode () (ou similaire) renvoyant un code de hachage pour cet objet. L'algorithme de code de hachage idéal et l'implémentation exacte dépendent des données représentées par l'objet.

2
Lucero

Une table de hachage fonctionne totalement sur le fait que le calcul pratique suit le modèle de la machine à accès aléatoire, c’est-à-dire qu’il est possible d’accéder à toute adresse en mémoire en O(1) heure ou en temps constant.

Donc, si j’ai un univers de clés (ensemble de toutes les clés possibles que je peux utiliser dans une application, par exemple le numéro de rouleau pour élève, s’il s’agit de 4 chiffres, cet univers est un ensemble de nombres compris entre 1 et 9999) et un moyen de les mapper à un ensemble fini de nombres de taille, je peux allouer de la mémoire dans mon système, théoriquement ma table de hachage est prête.

Généralement, dans les applications, la taille de l’univers de clés est très grande par rapport au nombre d’éléments que je souhaite ajouter à la table de hachage (je ne veux pas gaspiller une mémoire de 1 Go pour un hachage, disons 10000 ou 100000, car ils sont 32 peu de temps en reprsentaion binaire). Donc, nous utilisons ce hachage. C'est une sorte d'opération "mathématique" qui mélange mon grand univers à un petit ensemble de valeurs que je peux accepter en mémoire. Dans les cas pratiques, l’espace d’une table de hachage est souvent du même "ordre" (gros-O) que le (nombre d’éléments * taille de chaque élément). Nous ne perdons donc pas beaucoup de mémoire.

Maintenant, un grand ensemble mappé sur un petit ensemble, le mappage doit être plusieurs à un. Ainsi, différentes clés se verront attribuer le même espace (?? non équitable). Il y a plusieurs façons de gérer cela, je connais juste les deux populaires:

  • Utilisez l'espace à allouer à la valeur comme référence à une liste liée. Cette liste liée stockera une ou plusieurs valeurs qui résideront dans le même emplacement dans plusieurs mappages. La liste liée contient également des clés pour aider quelqu'un qui vient chercher. C'est comme si beaucoup de gens dans le même appartement, quand un livreur vient, il va dans la chambre et demande spécifiquement le type.
  • Utilisez une fonction de double hachage dans un tableau qui donne la même séquence de valeurs à chaque fois plutôt qu'une seule valeur. Lorsque je vais stocker une valeur, je vois si l'emplacement de mémoire requis est libre ou occupé. Si c'est gratuit, je peux y stocker ma valeur, s'il est occupé, je prends la valeur suivante de la séquence et ainsi de suite jusqu'à ce que je trouve un emplacement libre et que j'y stocke ma valeur. Lors de la recherche ou de la récupération de la valeur, je retourne sur le même chemin que celui indiqué par la séquence et, à chaque emplacement, je demande la valeur s'il est là jusqu'à ce que je la trouve ou que je recherche tous les emplacements possibles du tableau.

Introduction aux algorithmes par CLRS fournit un très bon aperçu du sujet.

2
div

Pour tous ceux qui recherchent le langage de programmation, voici comment cela fonctionne. La mise en œuvre interne de tables de hachage avancées comporte de nombreuses subtilités et optimisations pour l’allocation/désallocation de stockage et la recherche, mais l’idée de haut niveau sera sensiblement la même.

(void) addValue : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   if (bucket) 
   {
       //do nothing, just overwrite
   }
   else   //create bucket
   {
      create_extra_space_for_bucket();
   }
   put_value_into_bucket(bucket,value);
}

(bool) exists : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   return bucket;
}

calculate_bucket_from_val() est la fonction de hachage où toute la magie de l'unicité doit avoir lieu.

La règle de base est la suivante: Pour qu'une valeur donnée soit insérée, le compartiment doit être UNIQUE ET DÉRIVABLE DE LA VALEUR qu'il est censé stocker.

Bucket désigne tout espace dans lequel les valeurs sont stockées - je l'ai conservé ici sous forme d'indice de tableau, mais il peut également s'agir d'un emplacement de mémoire.

0
Nirav Bhatt