Articulo de referencia

Doble hash

El doble hash es una técnica de programación informática que se utiliza junto con el direccionamiento abierto en tablas hash para resolver colisiones de hash , utilizando un has...

El doble hash es una técnica de programación informática que se utiliza junto con el direccionamiento abierto en tablas hash para resolver colisiones de hash , utilizando un hash secundario de la clave como desplazamiento cuando se produce una colisión. El doble hash con direccionamiento abierto es una estructura de datos clásica en una tabla.T{\displaystyle T}.

La técnica de doble hash utiliza un valor hash como índice en la tabla y luego avanza repetidamente un intervalo hasta que se localiza el valor deseado, se alcanza una ubicación vacía o se ha explorado toda la tabla; pero este intervalo lo establece una segunda función hash independiente . A diferencia de los métodos alternativos de resolución de colisiones de sondeo lineal y sondeo cuadrático , el intervalo depende de los datos, de modo que los valores que se asignan a la misma ubicación tienen diferentes secuencias de cubetas; esto minimiza las colisiones repetidas y los efectos de la agrupación .

Dadas dos funciones hash aleatorias, uniformes e independientesh1{\displaystyle h_{1}}yh2{\displaystyle h_{2}}, eli{\displaystyle i}ubicación en la secuencia de cubetas para el valorincógnita{\displaystyle x}en una tabla hash de|T|{\displaystyle |T|}cubos es:h(i,incógnita)=(h1(incógnita)+ih2(incógnita))mod|T|.{\displaystyle h(i,x)=(h_{1}(x)+i\cdot h_{2}(x)){\bmod {|}}T|.}Las ubicaciones se pueden calcular convenientemente incrementando el hash anterior enh2(incógnita){\displaystyle h_{2}(x)}, es decirh(i+1,incógnita)=(h(i,incógnita)+h2(incógnita))mod|T|.{\displaystyle h(i+1,x)=(h(i,x)+h_{2}(x)){\bmod {|}}T|.}

Generalmente,h1{\displaystyle h_{1}}yh2{\displaystyle h_{2}}se seleccionan de un conjunto de funciones hash universales ;h1{\displaystyle h_{1}}se selecciona para tener una gama de{0,|T|1}{\displaystyle \{0,|T|-1\}}yh2{\displaystyle h_{2}}tener una gama de{1,|T|1}{\displaystyle \{1,|T|-1\}}El doble hash se aproxima a una distribución aleatoria; más precisamente, las funciones hash independientes por pares producen una probabilidad de(norte/|T|)2{\displaystyle (n/|T|)^{2}}que cualquier par de claves seguirá la misma secuencia de cubos.

Selección de h 2 (x)

La función hash secundariah2(incógnita){\displaystyle h_{2}(x)}debe tener varias características: [ 1 ]

  1. Nunca debería devolver un índice de cero. Cuando se devuelve 0, solo se examina un índice.
  2. Todoh2(incógnita){\displaystyle h_{2}(x)}debería ser relativamente primordial para|T|{\displaystyle |T|}De lo contrario, el número de índices sondeados sobre k elementos seríamin(k,|T|/h2(incógnita)){\displaystyle \min(k,|T|/h_{2}(x))}, que podría ser tan pequeño como 2.
  3. Debería recorrer toda la tabla.
  4. Debería ser muy rápido de calcular.
  5. Debe ser independiente por pares deh1(incógnita){\displaystyle h_{1}(x)}.

Las características de distribución deh2{\displaystyle h_{2}}son irrelevantes. Es análogo a un generador de números aleatorios.

En la práctica:

  • Si se utiliza el hash de división para ambas funciones, los divisores se eligen como números primos.
  • Si|T|{\displaystyle |T|}es una potencia de 2, los dos primeros requisitos generalmente se satisfacen haciendoh2(incógnita){\displaystyle h_{2}(x)}siempre devuelve un número impar estableciendo el bit menos significativo en 1 (h2(incógnita)=h2,original(incógnita)|1{\displaystyle h_{2}(x)=h_{2,{\text{orig}}}(x)|1}). Esto tiene el efecto secundario de duplicar la probabilidad de colisión debido a un bit desperdiciado. [ 1 ]

Análisis

Supongamos que se seleccionan dos funciones hash.h1{\displaystyle h_{1}}yh2{\displaystyle h_{2}}y se insertanα|T|{\displaystyle \alpha |T|}elementos en una tabla hash con|T|{\displaystyle |T|}ranuras. Supongamos que cada inserción de una claveincógnita{\displaystyle x}coloca la llave en la primera ranura disponible de la secuenciah(0,incógnita),h(1,incógnita),h(2,incógnita),{\displaystyle h(0,x),h(1,x),h(2,x),\ldots }definido por

h(i,incógnita)=(h1(incógnita)+ih2(incógnita))mod|T|.{\displaystyle h(i,x)=(h_{1}(x)+i\cdot h_{2}(x)){\bmod {|}}T|.}

Dada esta configuración, los análisis teóricos buscan determinar el tiempo necesario para realizar una inserción adicional (o, equivalentemente, el tiempo necesario para realizar una búsqueda infructuosa). Guibas y Szemerédi [ 2 ] demostraron en 1978 que, si h1{\displaystyle h_{1}}yh2{\displaystyle h_{2}}son uniformemente aleatorios yα<0,319{\displaystyle \alpha <0,319}, entonces el tiempo esperado esO(1){\displaystyle O(1)}. El trabajo posterior de Lueker y Molodowitch [ 3 ] demostró un límite de1/(1α){\displaystyle 1/(1-\alpha )}para cualquierα{\displaystyle \alpha }y establecieron que el comportamiento de la tabla hash puede acoplarse directamente al de una solución estándar basada en sondeo aleatorio. Mucho más recientemente, en 2007, Bradford y Katehakis [ 4 ] demostraron que incluso el uso de funciones hash universales , en lugar de funciones totalmente aleatorias, es suficiente para obtener una1/(1α){\displaystyle 1/(1-\alpha )}atado.

Al igual que otros métodos de direccionamiento abierto, el doble hash se vuelve lineal a medida que la tabla hash se acerca a su capacidad máxima. La heurística habitual consiste en limitar la carga de la tabla al 75 % de su capacidad. Eventualmente, será necesario volver a generar el hash para aumentar su tamaño, como ocurre con todos los demás esquemas de direccionamiento abierto.

Variantes

La tesis doctoral de Peter Dillinger señala que el doble hash produce funciones hash equivalentes no deseadas cuando las funciones hash se tratan como un conjunto, como en los filtros de Bloom : Sih2(y)=h2(incógnita){\displaystyle h_{2}(y)=-h_{2}(x)}yh1(y)=h1(incógnita)+kh2(incógnita){\displaystyle h_{1}(y)=h_{1}(x)+k\cdot h_{2}(x)}, entoncesh(i,y)=h(ki,incógnita){\displaystyle h(i,y)=h(ki,x)}y los conjuntos de hashes{h(0,incógnita),...,h(k,incógnita)}={h(0,y),...,h(k,y)}{\displaystyle \left\{h(0,x),...,h(k,x)\right\}=\left\{h(0,y),...,h(k,y)\right\}} son idénticos. Esto hace que una colisión sea dos veces más probable que lo esperado.1/|T|2{\displaystyle 1/|T|^{2}}. [ 5 ]

Además, hay un número significativo de conjuntos hash que se superponen en su mayoría; sih2(y)=h2(incógnita){\displaystyle h_{2}(y)=h_{2}(x)}yh1(y)=h1(incógnita)±h2(incógnita){\displaystyle h_{1}(y)=h_{1}(x)\pm h_{2}(x)}, entoncesh(i,y)=h(i±1,incógnita){\displaystyle h(i,y)=h(i\pm 1,x)}y comparando valores hash adicionales (ampliando el rango dei{\displaystyle i}) no sirve de nada.

Triple hash

Agregar un tercer hash como un término cuadrático ( triple hash ) hace que la superposición sea mucho menos probable, ya que ahora las clases equivalentes deben generarse mediante una colaboración de ambos.h2(incógnita){\displaystyle h_{2}(x)}yh3(incógnita){\displaystyle h_{3}(x)}, a un costo de un 50% más de cálculos debido a la función hash añadida. Opciones para el factor para estoh3(incógnita){\displaystyle h_{3}(x)}incluiri2{\displaystyle i^{2}}[ 6 ] y losnúmeros triangularesi(i±1)/2{\displaystyle i(i\pm 1)/2}. La función hash añadida debe cumplir los mismos requisitos que los enumerados anteriormente parah2(incógnita){\displaystyle h_{2}(x)}. [ 1 ]

El uso de los números triangulares facilita el cálculo del valor mediante la diferenciación hacia adelante : para eli(i2)/2{\displaystyle i(i-2)/2}variedad, [ 1 ]

from collections.abc import Iterator , Callable from typing import TypeVarT = TypeVar ( 'T' ) hashfunc = Callable [ T , int ] # se asume que h1, h2, h3 están definidos y son de tipo hashfuncMÓDULO = ( 1 << 32 )def triple_hash ( key : T , k : int ) -> Iterator [ int ]: """Devuelve k iteraciones de un triple hash.""" x , y , z = h1 ( key ), h2 ( key ), h3 ( key ) yield x for i in range ( 1 , k - 1 ): x = ( x + y ) % MODULUS y = ( y + z ) % MODULUS yield x

Este tipo de construcción no elimina por completo los conjuntos equivalentes. Si:

h1(y)=h1(incógnita)+kh2(incógnita)+k2h3(incógnita),{\displaystyle h_{1}(y)=h_{1}(x)+k\cdot h_{2}(x)+k^{2}\cdot h_{3}(x),}
h2(y)=h2(incógnita)2kh3(incógnita),{\displaystyle h_{2}(y)=-h_{2}(x)-2k\cdot h_{3}(x),}y
h3(y)=h3(incógnita).{\displaystyle h_{3}(y)=h_{3}(x).}

entonces

h(ki,y)=h1(y)+(ki)h2(y)+(ki)2h3(y)=h1(y)+(ki)(h2(incógnita)2kh3(incógnita))+(ki)2h3(incógnita)==h1(incógnita)+kh2(incógnita)+k2h3(incógnita)+(ik)h2(incógnita)+(i2k2)h3(incógnita)=h1(incógnita)+ih2(incógnita)+i2h3(incógnita)=h(i,incógnita).{\displaystyle {\begin{aligned}h(ki,y)&=h_{1}(y)+(ki)\cdot h_{2}(y)+(ki)^{2}\cdot h_{3}(y)\\&=h_{1}(y)+(ki)(-h_{2}(x)-2kh_{3}(x))+(ki)^{2}h_{3}(x)\\&=\ldots \\&=h_{1}(x)+kh_{2}(x)+k^{2}h_{3}(x)+(ik)h_{2}(x)+(i^{2}-k^{2})h_{3}(x)\\&=h_{1}(x)+ih_{2}(x)+i^{2}h_{3}(x)\\&=h(i,x).\\\end{aligned}}}

Doble hash mejorado

Agregar un término cúbicoi3{\displaystyle i^{3}}[ 6 ] o(i3i)/6{\displaystyle (i^{3}-i)/6}(un número tetraédrico ), [ 1 ] sí resuelve el problema, una técnica conocida como doble hash mejorado . El número tetraédrico se puede calcular de manera eficiente mediante diferenciación hacia adelante :

struct key ; /// Opaco /// Reemplace "unsigned int" con otros tipos según sea necesario. (Debe ser unsigned para garantizar el encapsulamiento). typedef unsigned int hashfunc ( struct key const * ); extern hashfunc h1 , h2 ;/// Calcula k valores hash a partir de dos funciones hash subyacentes /// h1() y h2() utilizando doble hash mejorado. Al regresar, /// hashes[i] = h1(x) + i*h2(x) + (i*i*i - i)/6. /// Aprovecha el encapsulamiento automático (reducción modular) /// de los tipos sin signo en C. void ext_dbl_hash ( struct key const * x , unsigned int hashes [], unsigned int n ) { unsigned int a = h1 ( x ), b = h2 ( x ), i = 0 ;hashes [ i ] = a ; para ( i = 1 ; i < n ; i ++ ) { a += b ; // Sumar diferencia cuadrática para obtener cúbica b += i ; // Sumar diferencia lineal para obtener cuadrática // i++ suma diferencia constante para obtener lineal hashes [ i ] = a ; } }

Además de rectificar el problema de colisión , el doble hash mejorado también elimina las restricciones numéricas del doble hash enh2(incógnita){\displaystyle h_{2}(x)}propiedades de , permitiendo una función hash similar en propiedades a (pero aún independiente de)h1{\displaystyle h_{1}}para ser utilizado. (Utilizando la numeración en § Selección , se eliminan los dos primeros requisitos.) [ 1 ]

Véase también

Referencias

  1. 1 2 3 4 5 6 Dillinger, Peter C.; Manolios, Panagiotis (15-17 de noviembre de 2004). Filtros de Bloom en la verificación probabilística (PDF) . 5ª Conferencia Internacional sobre Métodos Formales en Diseño Asistido por Computadora (FMCAD 2004). Austin, Texas. CiteSeerX 10.1.1.119.628 . doi : 10.1007/978-3-540-30494-4_26 . 
  2. Guibas; Szemeredi (1978). "El análisis del doble hash". J. Comput. System Sci . 16 (2): 226– 274. doi : 10.1016/0022-0000(78)90046-6 .
  3. Lueker, George; Molodowitch, Mariko (1988). «Análisis más profundo del doble hash». Actas del vigésimo simposio anual de la ACM sobre Teoría de la Computación - STOC '88 . Nueva York, Nueva York, EE. UU.: ACM Press. págs. 354–359 . doi : 10.1145/62212.62246 . ISBN  0-89791-264-0.
  4. Bradford, Phillip G.; Katehakis, Michael N. (abril de 2007), "Un estudio probabilístico sobre expansores combinatorios y funciones hash" (PDF) , SIAM Journal on Computing , 37 (1): 83–111 , doi : 10.1137/S009753970444630X , MR 2306284 , archivado del original (PDF) el 25 de enero de 2016 .
  5. Dillinger, Peter C. (diciembre de 2010). Almacenamiento de estado aproximado adaptativo (PDF) (tesis doctoral). Northeastern University. págs. 93–112 . 
  6. 1 2 Kirsch, Adam; Mitzenmacher, Michael (septiembre de 2008). "Menos hash, mismo rendimiento: construyendo un mejor filtro de Bloom" (PDF) . Random Structures and Algorithms . 33 (2): 187– 218. CiteSeerX 10.1.1.152.579 . doi : 10.1002/rsa.20208 . 
  • Cómo afecta el almacenamiento en caché al hashing, por Gregory L. Heileman y Wenbin Luo, 2005.
  • Animación de tabla hash
  • klib es una biblioteca de C que incluye funcionalidad de doble hash.