Je travaille sur la table de hachage en langage C et je teste la fonction de hachage pour string.
La première fonction que j'ai essayée consiste à ajouter du code ASCII et à utiliser modulo (100%), mais le résultat du premier test de données est médiocre: 40 collisions pour 130 mots.
Les données finales d’entrée contiendront 8 000 mots (c’est un dictionnaire qui stocke dans un fichier). La table de hachage est déclarée en tant que table int [10000] et contient la position du mot dans un fichier txt.
La première question est de savoir quel est le meilleur algorithme pour la chaîne de hachage? et comment déterminer la taille de la table de hachage?
merci d'avance !
:-)
J'ai eu de bons résultats avec djb2
par Dan Bernstein.
_unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
_
Tout d'abord, vous ne voulez généralement pas utiliser un hachage cryptographique pour une table de hachage. Un algorithme qui est très rapide par rapport aux normes cryptographiques est toujours extrêmement lent par rapport aux normes par table de hachage.
Deuxièmement, vous voulez vous assurer que chaque bit de l'entrée peut/va affecter le résultat. Une méthode simple consiste à faire pivoter le résultat actuel d’un nombre de bits, puis XOR le code de hachage actuel avec l’octet actuel. Répétez jusqu'à la fin de la chaîne. Notez que vous ne voulez généralement pas que la rotation soit un multiple pair de la taille en octets.
Par exemple, dans le cas courant d'octets de 8 bits, vous pouvez effectuer une rotation de 5 bits:
int hash(char const *input) {
int result = 0x55555555;
while (*input) {
result ^= *input++;
result = rol(result, 5);
}
}
Edit: Notez également que 10000 emplacements est rarement un bon choix pour une taille de table de hachage. Vous voulez généralement une des deux choses: soit vous voulez un nombre premier comme taille (nécessaire pour assurer la correction avec certains types de résolution de hachage), soit une puissance de 2 (afin de réduire la valeur à la plage correcte peut être fait avec un simple bit-masque).
Il existe un certain nombre d'implémentations de hashtables existantes pour C, de la bibliothèque standard C hcreate/hdestroy/hsearch à celles de APR et glib , qui fournissent également des préconfigurations prédéfinies. fonctions de hachage. Je recommande fortement d'utiliser ceux-ci plutôt que d'inventer votre propre hashtable ou fonction de hachage; ils ont été fortement optimisés pour les cas d'utilisation courants.
Si votre jeu de données est statique, cependant, votre meilleure solution consiste probablement à utiliser un hash parfait . gperf générera un hachage parfait pour un jeu de données donné.
Wikipédia montre une belle fonction de hachage de chaîne appelée Hache Jenkins One At A Time. Il cite également des versions améliorées de ce hachage.
uint32_t jenkins_one_at_a_time_hash(char *key, size_t len)
{
uint32_t hash, i;
for(hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
Bien que djb2
, comme présenté sur stackoverflow par cnicutar , est certainement meilleur, je pense que cela vaut la peine de montrer le hash K & R :
1) Apparemment, un algorithme de hachage terrible , tel que présenté dans la 1ère édition de K & R ( source )
_unsigned long hash(unsigned char *str)
{
unsigned int hash = 0;
int c;
while (c = *str++)
hash += c;
return hash;
}
_
2) Probablement un algorithme de hachage plutôt correct, présenté dans K & R version 2 (vérifié par moi à la page 144 du livre); NB: assurez-vous de supprimer _% HASHSIZE
_ de l'instruction return si vous prévoyez d'utiliser le module dimensionnement à la longueur de votre tableau en dehors de l'algorithme de hachage. Aussi, je vous recommande de faire le retour et "hashval" tapez _unsigned long
_ au lieu du simple unsigned
(int).
_unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31*hashval;
return hashval % HASHSIZE;
}
_
Notez que les deux algorithmes indiquent clairement que le hachage de la 1ère édition est si terrible parce qu’il ne prend PAS en considération le caractère de chaîne order , donc hash("ab")
serait donc renvoie la même valeur que hash("ba")
. Ceci est pas , donc avec le hash de la 2e édition, cependant, ce qui (bien mieux!) Renverrait deux valeurs différentes pour ces chaînes.
Fonctions de hachage de GCC C++ 11 utilisées pour unordered_map
(un modèle de table de hachage) et unordered_set
(un modèle de jeu de hachage) semble être comme suit.
Code:
_// Implementation of Murmur hash for 32-bit size_t.
size_t _Hash_bytes(const void* ptr, size_t len, size_t seed)
{
const size_t m = 0x5bd1e995;
size_t hash = seed ^ len;
const char* buf = static_cast<const char*>(ptr);
// Mix 4 bytes at a time into the hash.
while (len >= 4)
{
size_t k = unaligned_load(buf);
k *= m;
k ^= k >> 24;
k *= m;
hash *= m;
hash ^= k;
buf += 4;
len -= 4;
}
// Handle the last few bytes of the input array.
switch (len)
{
case 3:
hash ^= static_cast<unsigned char>(buf[2]) << 16;
[[gnu::fallthrough]];
case 2:
hash ^= static_cast<unsigned char>(buf[1]) << 8;
[[gnu::fallthrough]];
case 1:
hash ^= static_cast<unsigned char>(buf[0]);
hash *= m;
};
// Do a few final mixes of the hash.
hash ^= hash >> 13;
hash *= m;
hash ^= hash >> 15;
return hash;
}
_
J'ai essayé ces fonctions de hachage et obtenu le résultat suivant. J'ai environ 960 ^ 3 entrées de 64 octets chacune, 64 caractères dans un ordre différent, valeur de hachage 32 bits. Codes de ici .
Hash function | collision rate | how many minutes to finish
MurmurHash3 | 6.?% | 4m15s
Jenkins One.. | 6.1% | 6m54s
Bob, 1st in link| 6.16% | 5m34s
SuperFastHash | 10% | 4m58s
bernstein | 20% | 14s only finish 1/20
one_at_a_time | 6.16% | 7m5s
crc | 6.16% | 7m56s
Une chose étrange est que presque toutes les fonctions de hachage ont un taux de collision de 6% pour mes données.
Premièrement, est-ce que 40 collisions pour 130 mots sont hachées à 0..99, est-ce mauvais? Vous ne pouvez pas vous attendre à un hachage parfait si vous ne prenez pas les mesures nécessaires pour que cela se produise. Une fonction de hachage ordinaire n'aura pas moins de collisions qu'un générateur aléatoire la plupart du temps.
Une fonction de hachage avec une bonne réputation est MurmurHash .
Enfin, en ce qui concerne la taille de la table de hachage, cela dépend vraiment du type de table de hachage que vous avez en tête, en particulier si les compartiments sont extensibles ou à un seul emplacement. Si les compartiments sont extensibles, là encore, vous avez le choix: vous choisissez la longueur moyenne du compartiment pour les contraintes de mémoire/vitesse dont vous disposez.
Une chose que j’ai utilisée avec de bons résultats est la suivante (je ne sais pas si elle est déjà mentionnée car je ne me souviens plus de son nom).
Vous calculez un tableau T avec un nombre aléatoire pour chaque caractère de l'alphabet de votre clé [0,255]. Vous hachez votre clé 'k0 k1 k2 ... kN' en prenant T [k0] xor T [k1] xor ... xor T [kN]. Vous pouvez facilement montrer que cela est aussi aléatoire que votre générateur de nombres aléatoires et que son calcul est très réalisable. Si vous rencontrez un problème très grave avec de nombreuses collisions, vous pouvez simplement répéter l'opération en utilisant un nouveau lot de nombres aléatoires.