Table de hachage
Une table de hachage est, en informatique, une structure de donnĂ©es qui permet une association clĂ©âvaleur, c'est-Ă -dire une implĂ©mentation du type abstrait tableau associatif. Son but principal est de permettre de retrouver une clĂ© donnĂ©e trĂšs rapidement, en la cherchant Ă un emplacement de la table correspondant au rĂ©sultat d'une fonction de hachage calculĂ©e en O(1). Cela constitue un gain de temps trĂšs important pour les grosses tables, lors d'une recherche ou d'un besoin d'accĂšs aux donnĂ©es en utilisant la clĂ© dĂ©finie.
Il s'agit d'un tableau ne comportant pas d'ordre (contrairement à un tableau ordinaire qui est indexé par des entiers). On accÚde à chaque valeur du tableau par sa clé, qui transformée par une fonction de hachage en une valeur de hachage (un nombre) indexe les éléments de la table, ces derniers étant appelés alvéoles (en anglais, buckets ou slots).
Le fait de crĂ©er une valeur de hachage Ă partir d'une clĂ© peut engendrer un problĂšme de collision, câest-Ă -dire que deux clĂ©s diffĂ©rentes, voire davantage, pourront se retrouver associĂ©es Ă la mĂȘme valeur de hachage et donc Ă la mĂȘme alvĂ©ole (les fonctions de hachage ne sont pas injectives). Pour diminuer les risques de collisions, il faut donc premiĂšrement choisir avec soin sa fonction de hachage. Ensuite, un mĂ©canisme de rĂ©solution des collisions sera Ă implĂ©menter si nĂ©cessaire. Il faudra alors stocker dans les alvĂ©oles la paire clĂ©âvaleur et pas uniquement la valeur, afin de pouvoir comparer la clĂ© avec celle qui sera donnĂ©e en entrĂ©e.
Tout comme les tableaux ordinaires, les tables de hachage permettent un accĂšs en O(1) en moyenne, quel que soit le nombre de paires clĂ©âvaleur dans la table. Toutefois, comme plusieurs paires clĂ©âvaleur peuvent se trouver dans la mĂȘme alvĂ©ole, le temps d'accĂšs dans le pire des cas peut ĂȘtre de O(n). ComparĂ©es aux autres tableaux associatifs, les tables de hachage sont surtout utiles lorsque le nombre de paires clĂ©âvaleur est trĂšs important.
La position des paires clĂ©âvaleur dans une table de hachage est pseudo-alĂ©atoire mais dĂ©pend de la fonction de hachage choisie et des clĂ©s utilisĂ©es. Cette structure n'est donc pas adaptĂ©e au feuilletage (browsing) de donnĂ©es voisines. Des types de structures de donnĂ©es comme les arbres Ă©quilibrĂ©s, gĂ©nĂ©ralement plus lents (en O(log n)) et un peu plus complexes Ă implĂ©menter, maintiennent une structure ordonnĂ©e.
Fonction de hachage
L'idĂ©e gĂ©nĂ©rale de la fonction de hachage est de rĂ©partir les paires clĂ©âvaleur dans un tableau d'alvĂ©oles. Une fonction de hachage permet de transformer une clĂ© en une valeur de hachage, donnant ainsi la position d'une alvĂ©ole dans le tableau.
Le calcul de la valeur de hachage se fait parfois en deux temps :
- Une fonction de hachage particuliÚre à l'application est utilisée pour produire un nombre entier à partir de la clé ;
- Ce nombre entier est converti en une position possible de la table, en général en calculant le reste modulo la taille de la table.
Si la clé n'est pas un entier naturel, il faut trouver un moyen de la considérer comme tel. Par exemple, si la clé est une chaine de caractÚres, on peut prendre les valeurs numériques (ASCII ou autre) de chaque caractÚre et les combiner par une fonction rapide comme le ou exclusif, donnant un entier qui servira d'index. Dans la pratique, on cherche à éviter le recours aux divisions à cause de leur relative lenteur sur certaines machines.
Choix d'une fonction de hachage
Une bonne fonction de hachage est utile aux performances, surtout si les collisions sont résolues ensuite par des explorations séquentielles : toute fonction de hachage provoquant beaucoup de collisions (par exemple la fonction qui à une clé associe la valeur ASCII de son premier caractÚre) ralentira nettement la recherche. Un bon compromis est à trouver entre :
- la rapidité de la fonction de hachage ;
- la taille à réserver pour l'espace de hachage ;
- la réduction du risque des collisions.
Un ou exclusif de tous les caractÚres d'une clé fournissait souvent un compromis acceptable dans l'écriture de compilateurs au début des années 1960.
Larry Wall utilisa pour implémenter son langage Perl une fonction permettant de doubler autant de fois que nécessaire avec l'extension du nombre de clés l'espace de hachage, sans autre nouveau calcul des clés qu'une translation binaire.
Les tailles des tables de hachage sont souvent des nombres premiers, afin d'éviter les problÚmes de diviseurs communs, qui créeraient un nombre important de collisions. Une autre possibilité est d'utiliser une puissance de deux, ce qui permet de réaliser l'opération modulo par de simples décalages, et donc de gagner en rapidité.
Un problÚme fréquent est le phénomÚne de grumelage (clustering) qui désigne le fait que des valeurs de hachage se retrouvent cÎte à cÎte dans la table, formant des « grumeaux ». Cela pénalise ces méthodes. Les fonctions de hachage réalisant une distribution uniforme des valeurs de hachage sont à rechercher, mais dÚs lors que le nombre de clés devient voisin du tiers de la taille de la table ces collisions deviennent probables[note 1].
Quand un attaquant essaye de pénaliser la recherche en soumettant des clés générant un grand nombre de collisions afin de la ralentir (voire de provoquer un déni de service), une solution possible est de choisir aléatoirement une fonction de hachage au début du programme. L'adversaire n'a alors pas de moyen de connaßtre le type de données qui produira des collisions. Dans la pratique, les algorithmes sont le plus souvent conçus pour affronter des données réparties de maniÚre aléatoire que contre un adversaire.
Fonction de hachage parfaite et minimale
Une fonction de hachage est dite parfaite si elle n'engendre aucune collision. Si toutes les clĂ©s sont connues, une fonction de hachage parfaite peut ĂȘtre utilisĂ©e pour crĂ©er une table de hachage parfaite sans aucune collision.
Une fonction de hachage est dite minimale si elle est parfaite et répartit n entrées dans une table de exactement n alvéoles.
Une fonction de hachage parfaite permet un accĂšs en temps constant dans tous les cas.
Facteur de compression
Le facteur de compression (load factor) qui est la proportion d'alvéoles utilisées dans une table de hachage est une indication critique de ses performances.
Il est défini ainsi :
oĂč
- n est le nombre de paires clĂ©âvaleur ;
- k est le nombre d'alvéoles.
Modification de la taille de la table
Lorsque le facteur de compression de la table augmente au-delĂ de 50 %, les collisions deviennent frĂ©quentes. Une solution est d'augmenter la taille de la table (c'est-Ă -dire le nombre d'alvĂ©oles) sitĂŽt atteint ce taux, tout en maintenant cette taille Ă un nombre premier ou Ă une puissance de 2 selon que l'on travaille par division (lent) ou modulo (bien plus rapide). S'il y a des dizaines de milliers de clĂ©s ou davantage, le calcul d'une nouvelle valeur doit ĂȘtre aussi rapide que possible â il doit si possible se dĂ©duire Ă partir de la clĂ© antĂ©rieure, idĂ©alement par simple dĂ©calage binaire (shift).
Le rehachage (rehash) est une fonction qui, en général, double (au moins) l'espace mémoire alloué pour la table, et recopie parfois ses valeurs (temps d'exécution en O(n)). Cette fonction cherche le plus petit nombre premier supérieur à deux fois sa taille.
Dans la pratique, les fonctions idĂ©ales dĂ©pendent des progrĂšs relatifs des vitesses de calcul des processeurs et des temps d'accĂšs Ă la mĂ©moire : des choix bien adaptĂ©s Ă une gĂ©nĂ©ration de machines pourront ne plus l'ĂȘtre pour la suivante.
RĂ©solution des collisions
Lorsque deux clĂ©s ont la mĂȘme valeur de hachage, les paires clĂ©âvaleur associĂ©es sont stockĂ©es dans la mĂȘme alvĂ©ole. On doit alors employer une mĂ©thode de rĂ©solution des collisions.
Le calcul probabiliste montre que mĂȘme si la fonction de hachage a une distribution parfaitement uniforme, il y a 95 % de chances d'avoir une collision dans une table de hachage Ă 1 million d'alvĂ©oles avant mĂȘme qu'elle ne contienne 2 500 paires clĂ©âvaleur. Les collisions ne posent cependant de rĂ©el problĂšme que si elles sont nombreuses au mĂȘme endroit. MĂȘme une collision unique sur chaque clĂ© utilisĂ©e n'a pas d'effet trĂšs perceptible.
Plusieurs mĂ©thodes de traitement des collisions existent. Les plus utilisĂ©es sont le chaĂźnage et l'adressage ouvert. Depuis le dĂ©but des annĂ©es 1990, les dĂ©veloppeurs de logiciels n'ont plus Ă se prĂ©occuper vraiment du dĂ©tail de ces mĂ©thodes, celles-ci Ă©tant directement incorporĂ©es dans les langages eux-mĂȘmes â Perl, PHP, REXX, etc. â ou au pire dans les objets de la bibliothĂšque qu'ils utilisent.
ChaĂźnage
Cette mĂ©thode est la plus simple. Chaque alvĂ©ole de la table est une liste chaĂźnĂ©e des paires clĂ©âvaleur qui ont la mĂȘme valeur de hachage. Une fois l'alvĂ©ole trouvĂ©e, la recherche est alors linĂ©aire en la taille de la liste chaĂźnĂ©e. Dans le pire des cas oĂč la fonction de hachage renvoie toujours la mĂȘme valeur de hachage quelle que soit la clĂ©, la table de hachage devient alors une liste chaĂźnĂ©e, et le temps de recherche est en O(n). L'avantage du chaĂźnage est que la suppression d'une clĂ© est facile, et que l'agrandissement de la table peut ĂȘtre retardĂ© plus longtemps que dans l'adressage ouvert, les performances se dĂ©gradant moins vite.
D'autres structures de donnĂ©es que les listes chaĂźnĂ©es peuvent ĂȘtre utilisĂ©es. En utilisant un arbre Ă©quilibrĂ©, le coĂ»t thĂ©orique de recherche dans le pire des cas est en O(log n). Cependant, la liste Ă©tant supposĂ©e ĂȘtre courte, cette approche est en gĂ©nĂ©ral peu efficace Ă moins d'utiliser la table Ă sa pleine capacitĂ©, ou d'avoir un fort taux de collisions. Un tableau dynamique peut aussi ĂȘtre utilisĂ© pour rĂ©duire la perte d'espace mĂ©moire et amĂ©liorer les performances du cache lorsque le nombre de paires clĂ©âvaleur est petit.
Adressage ouvert
L'adressage ouvert consiste dans le cas d'une collision Ă stocker les paires clĂ©âvaleur dans d'autres alvĂ©oles. La position des alvĂ©oles est dĂ©terminĂ©e par une mĂ©thode de sondage. Lors d'une recherche, si l'alvĂ©ole obtenue par hachage direct ne permet pas d'obtenir la bonne clĂ©, une recherche sur les alvĂ©oles obtenues par une mĂ©thode de sondage est effectuĂ©e jusqu'Ă trouver la clĂ©, ou non, ce qui indique qu'aucune clĂ© de ce type n'appartient Ă la table.
Les méthodes de sondage courantes sont :
- le sondage linéaire : l'intervalle entre les alvéoles est fixe, souvent 1 ;
- le sondage quadratique : l'intervalle entre les alvĂ©oles augmente linĂ©airement (les indices des alvĂ©oles augmentent donc quadratiquement), ce qui peut s'exprimer par la formule : oĂč k est le nombre d'alvĂ©oles ;
- le double hachage : l'indice de l'alvéole est donné par une deuxiÚme fonction de hachage, ou hachage secondaire.
Le sondage linéaire possÚde la meilleure performance en termes de cache, mais est sensible à l'effet de grumelage décrit plus haut. Le double hachage ne permet pas d'utiliser le cache efficacement, mais permet de réduire presque complÚtement ce grumelage, au prix d'une complexité plus élevée. Le sondage quadratique se situe entre le linéaire et le double hachage au niveau des performances.
Plus le facteur de compression est proche de 100 %, plus le nombre de sondages Ă effectuer devient important. Lorsque la table est pleine, les algorithmes de sondage peuvent mĂȘme Ă©chouer. Le facteur de compression est en gĂ©nĂ©ral limitĂ© Ă 80 %, mĂȘme en disposant d'une bonne fonction de hachage.
Des facteurs de compression faibles ne sont pas pour autant synonymes de bonnes performances, une mauvaise fonction de hachage pouvant générer un grumelage. L'indexation par la valeur ASCII du premier caractÚre d'une variable laisserait par exemple nombre de cases inutilisées, tout en assurant un maximum de collisions sur des noms de variables tels que x1
, x2
, ⊠en calcul scientifique ou sur des prénoms répandus tels que Martin
, Michel
, ⊠en traitement d'annuaires.
Notes et références
Bibliographie
- (en) Donald E. Knuth, The Art of Computer Programming, vol. 3 : Sorting and Searching, , 2e Ă©d. [dĂ©tail de lâĂ©dition], partie 6, chap. 4 (« Hashing »), p. 513-558
- (en) Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest et Clifford Stein, Introduction à l'algorithmique [« Introduction to Algorithms »], MIT Press et McGraw-Hill, , 2e éd., 1180 p. (ISBN 978-0-262-03293-3, lire en ligne), chap. 11 (« Hash Tables »), p. 221-252
- (en) Michael T. Goodrich (en) et Roberto Tamassia (en), Data Structures and Algorithms in Java, John Wiley & Sons, , 4e éd., 720 p. (ISBN 978-0-471-73884-8), chap. 9 (« Maps and Dictionaries »), p. 369-418
- Christine Froidevaux, Marie-Claude Gaudel et MichÚle Soria, Types de données et algorithmes, McGraw-Hill, coll. « Informatique », , 575 p. (ISBN 978-2-84074-023-0)
Références
Notes
Article connexe
- Filtre de Bloom, une autre structure de données fondée sur les fonctions de hachage