Estoy trabajando en una tabla hash en lenguaje C y estoy probando la función hash para string.
La primera función que he intentado es agregar un código ASCII y usar el módulo (% 100), pero obtuve malos resultados con la primera prueba de datos: 40 colisiones por 130 palabras.
Los datos de entrada finales contendrán 8 000 palabras (es un almacén de archivos en un archivo). La tabla hash se declara como tabla int [10000] y contiene la posición de la palabra en un archivo txt.
La primera pregunta es ¿cuál es el mejor algoritmo para el hash string? y cómo determinar el tamaño de la tabla hash?
gracias por adelantado !
:-)
Respuestas:
He tenido buenos resultados con
djb2
Dan Bernstein.fuente
size_t
u otro valor sin signo (como el largo sin signo en este código). La persona que llama es responsable de tomar un módulo del resultado para ajustarlo a la tabla hash. La persona que llama controla la ranura de la tabla a la que se ha tropezado; No es la función. Simplemente devuelve un número sin signo.Primero, generalmente no desea utilizar un hash criptográfico para una tabla hash. Un algoritmo que es muy rápido según los estándares criptográficos sigue siendo insoportablemente lento según los estándares de la tabla hash.
En segundo lugar, desea asegurarse de que cada bit de la entrada puede / afectará el resultado. Una manera fácil de hacerlo es rotar el resultado actual en un cierto número de bits, luego XOR el código hash actual con el byte actual. Repita hasta llegar al final de la cuerda. Tenga en cuenta que, en general, tampoco desea que la rotación sea un múltiplo par del tamaño del byte.
Por ejemplo, suponiendo el caso común de bytes de 8 bits, puede rotar en 5 bits:
Editar: también tenga en cuenta que 10000 ranuras rara vez es una buena opción para un tamaño de tabla hash. Por lo general, desea una de dos cosas: desea un número primo como el tamaño (requerido para garantizar la corrección con algunos tipos de resolución hash) o una potencia de 2 (por lo que puede reducir el valor al rango correcto con un simple máscara de bits).
fuente
Wikipedia muestra una buena función hash de cadena llamada Jenkins One At A Time Hash. También cita versiones mejoradas de este hash.
fuente
Existen varias implementaciones de tablas hash existentes para C, desde la biblioteca estándar C hcreate / hdestroy / hsearch, hasta las de APR y glib , que también proporcionan funciones hash preconstruidas. Recomiendo usarlos en lugar de inventar su propia tabla hash o función hash; Se han optimizado en gran medida para casos de uso comunes.
Sin embargo, si su conjunto de datos es estático, su mejor solución es probablemente usar un hash perfecto . gperf generará un hash perfecto para usted para un conjunto de datos dado.
fuente
djb2 tiene 317 colisiones para este diccionario de inglés de 466k, mientras que MurmurHash no tiene ninguno para hashes de 64 bits y 21 para hashes de 32 bits (se esperan alrededor de 25 para hashes aleatorios de 466k de 32 bits). Mi recomendación es usar MurmurHash si está disponible, es muy rápido, ya que toma varios bytes a la vez. Pero si necesita una función hash simple y corta para copiar y pegar en su proyecto, le recomiendo usar soplos versión de un byte a la vez:
El tamaño óptimo de una tabla hash es, en resumen, tan grande como sea posible sin dejar de encajar en la memoria. Debido a que generalmente no sabemos o queremos buscar cuánta memoria tenemos disponible, e incluso podría cambiar, el tamaño óptimo de la tabla hash es aproximadamente 2 veces el número esperado de elementos que se almacenarán en la tabla. Asignar mucho más que eso hará que su tabla hash sea más rápida pero con rendimientos decrecientes rápidamente, haciendo que su tabla hash sea más pequeña que eso, la hará exponencialmente más lenta. Esto se debe a que existe una compensación no lineal entre el espacio y la complejidad del tiempo para las tablas hash, con un factor de carga óptimo de 2-sqrt (2) = 0.58 ... aparentemente.
fuente
Primero, ¿son 40 colisiones de 130 palabras hash a 0..99 mal? No puede esperar un hashing perfecto si no está tomando medidas específicas para que suceda. Una función hash ordinaria no tendrá menos colisiones que un generador aleatorio la mayor parte del tiempo.
Una función hash con buena reputación es MurmurHash3 .
Finalmente, con respecto al tamaño de la tabla hash, realmente depende del tipo de tabla hash que tenga en mente, especialmente si los cubos son extensibles o de una ranura. Si los depósitos son extensibles, nuevamente hay una opción: usted elige la longitud promedio del depósito para las restricciones de memoria / velocidad que tiene.
fuente
n - m * (1 - ((m-1)/m)^n) = 57.075...
. 40 colisiones es mejor de lo que podría esperarse por casualidad (46 a 70 con un puntaje p de 0.999). La función hash en cuestión es más uniforme que si fuera aleatoria o si presenciamos un evento muy raro.Sin embargo
djb2
, como se presenta en stackoverflow por cnicutar , es casi seguro que sea mejor, creo que vale la pena mostrar el K&R hashes de :1) Aparentemente un algoritmo hash terrible , como se presenta en K&R 1st edition ( fuente )
2) Probablemente un algoritmo hash bastante decente, como se presenta en K&R versión 2 (verificado por mí en la página 144 del libro); NB: asegúrese de eliminar
% HASHSIZE
de la declaración de devolución si planea hacer el tamaño del módulo a la longitud de su matriz fuera del algoritmo hash. Además, te recomiendo que hagas el tipo return y "hashval" enunsigned long
lugar del simpleunsigned
(int).Tenga en cuenta que, a partir de los dos algoritmos, está claro que una razón por la que el hash de la primera edición es tan terrible es porque NO toma en consideración el orden de los caracteres de la cadena , por
hash("ab")
lo que devolvería el mismo valor quehash("ba")
. Sin embargo, esto no es así con el hash de la 2da edición, que (¡mucho mejor!) Devolvería dos valores diferentes para esas cadenas.Las funciones de hash GCC C ++ 11 utilizadas para
unordered_map
(una plantilla de tabla hash) yunordered_set
(una plantilla de conjunto hash) parecen ser las siguientes.Código:
fuente
He probado estas funciones hash y obtuve el siguiente resultado. Tengo alrededor de 960 ^ 3 entradas, cada una de 64 bytes de largo, 64 caracteres en diferente orden, valor hash de 32 bits. Códigos de aquí .
Una cosa extraña es que casi todas las funciones hash tienen una tasa de colisión del 6% para mis datos.
fuente
Una cosa que he usado con buenos resultados es la siguiente (no sé si ya se mencionó porque no recuerdo su nombre).
Precalcula una tabla T con un número aleatorio para cada carácter en el alfabeto de su clave [0,255]. Hash tu clave 'k0 k1 k2 ... kN' tomando T [k0] xor T [k1] xor ... xor T [kN]. Puede demostrar fácilmente que esto es tan aleatorio como su generador de números aleatorios y que es computacionalmente muy factible y si realmente se encuentra con una instancia muy mala con muchas colisiones, puede repetir todo usando un nuevo lote de números aleatorios.
fuente