En informática , el hash dinámico perfecto es una técnica de programación para resolver colisiones en una estructura de datos de tabla hash . [1] [2] [3] Si bien consume más memoria que sus contrapartes de tabla hash, [ cita necesaria ] esta técnica es útil para situaciones en las que se deben realizar consultas, inserciones y eliminaciones rápidas en un gran conjunto de elementos.
El problema del hash estático óptimo fue resuelto por primera vez en general por Fredman, Komlós y Szemerédi. [4] En su artículo de 1984, [1] detallan un esquema de tabla hash de dos niveles en el que cada segmento de la tabla hash (de primer nivel) corresponde a una tabla hash separada de segundo nivel. Las claves se procesan dos veces: el primer valor hash se asigna a un determinado depósito en la tabla hash de primer nivel; el segundo valor hash proporciona la posición de esa entrada en la tabla hash de segundo nivel de ese depósito. Se garantiza que la tabla de segundo nivel estará libre de colisiones (es decir, hash perfecto ) durante la construcción. En consecuencia, se garantiza que el costo de búsqueda será O(1) en el peor de los casos . [2]
En el caso estático, se nos proporciona de antemano un conjunto con un total de x entradas, cada una con una clave única. Fredman, Komlós y Szemerédi eligen una tabla hash de primer nivel con depósitos de tamaño. [2]
Para construir, x entradas se separan en s depósitos mediante la función hash de nivel superior, donde . Luego, para cada depósito con k entradas, se asigna una tabla de segundo nivel con ranuras, y su función hash se selecciona al azar de un conjunto de funciones hash universal para que esté libre de colisiones (es decir, una función hash perfecta ) y se almacena junto con el tabla de picadillo. Si la función hash seleccionada aleatoriamente crea una tabla con colisiones, se selecciona aleatoriamente una nueva función hash hasta que se pueda garantizar una tabla libre de colisiones. Finalmente, con el hash sin colisiones, las k entradas se convierten en hash en la tabla de segundo nivel.
El tamaño cuadrático del espacio garantiza que la creación aleatoria de una tabla con colisiones sea poco frecuente e independiente del tamaño de k , lo que proporciona un tiempo de construcción amortizado lineal. Aunque cada tabla de segundo nivel requiere espacio cuadrático, si las claves insertadas en la tabla hash de primer nivel están distribuidas uniformemente , la estructura en su conjunto ocupa el espacio esperado, ya que los tamaños de los depósitos son pequeños con alta probabilidad . [1]
La función hash de primer nivel se elige específicamente de modo que, para el conjunto específico de x valores clave únicos, el espacio total T utilizado por todas las tablas hash de segundo nivel tenga el espacio esperado, y más específicamente . Fredman, Komlós y Szemerédi demostraron que dada una familia de funciones hash universal , al menos la mitad de esas funciones tienen esa propiedad. [2]
Dietzfelbinger et al. Presentar un algoritmo de diccionario dinámico que, cuando se agrega incrementalmente un conjunto de n elementos al diccionario, las consultas de membresía siempre se ejecutan en tiempo constante y, por lo tanto, en el peor de los casos, el almacenamiento total requerido es (lineal) y el tiempo de inserción y eliminación amortizado esperado. ( tiempo constante amortizado ).
En el caso dinámico, cuando se inserta una clave en la tabla hash, si su entrada en su subtabla respectiva está ocupada, se dice que ocurre una colisión y la subtabla se reconstruye en función de su nuevo recuento total de entradas y la función hash seleccionada aleatoriamente. Debido a que el factor de carga de la tabla de segundo nivel se mantiene bajo , la reconstrucción es poco frecuente y el costo esperado amortizado de las inserciones es . [2] De manera similar, el costo esperado amortizado de las eliminaciones es . [2]
Además, en el caso dinámico no se pueden conocer los tamaños finales de la tabla de nivel superior o de cualquiera de las subtablas. Un método para mantener el espacio esperado de la tabla es solicitar una reconstrucción completa cuando se haya producido un número suficiente de inserciones y eliminaciones. Según los resultados de Dietzfelbinger et al., [2] siempre que el número total de inserciones o eliminaciones exceda el número de elementos en el momento de la última construcción, el costo amortizado esperado de inserción y eliminación permanece teniendo en cuenta la repetición completa.
La implementación del hash dinámico perfecto de Dietzfelbinger et al. utiliza estos conceptos, así como la eliminación diferida , y se muestra en el pseudocódigo a continuación.
La función Localizar ( x ) es j : = h ( x ) si (la posición h j ( x ) de la subtabla T j contiene x (no eliminada)) regresa ( x está en S ) finaliza si no regresa ( x no está en S ) fin de lo contrario fin
Durante la inserción de una nueva entrada x en j , se incrementa el contador de operaciones globales, count .
Si x existe en j , pero está marcado como eliminado, entonces se elimina la marca.
Si x existe en j o en la subtabla T j y no está marcado como eliminado, entonces se dice que ocurre una colisión y la tabla de segundo nivel T j del jésimo depósito se reconstruye con una función hash h j diferente seleccionada aleatoriamente .
la función Insertar ( x ) es contar = contar + 1; si ( cuenta > M ) Rehash completo( x ); terminar si no j = h( x ); if (La posición h j (x) de la subtabla T j contiene x ) if ( x está marcado como eliminado) eliminar el marcador de eliminación; terminar si terminar si más b j = b j + 1; si ( b j <= m j ) si la posición h j ( x ) de T j está vacía almacenar x en la posición hj ( x ) de Tj ; end if else Ponga todos los elementos no marcados de T j en la lista L j ; Agregue x a la lista L j ; b j = longitud de L j ; repetir h j = función elegida al azar en H sj ; hasta que h j sea inyectivo sobre los elementos de L j ; para todo y en la lista L j, almacene y en la posición h j ( y ) de T j ; fin por fin si no fin si más m j = 2 * max{1, m j }; s j = 2 * m j * ( m j - 1); si la suma total de todos s j ≤ 32 * M 2 / s ( M ) + 4 * M Asigne s j celdas para T j ; Coloque todos los elementos no marcados de T j en la lista L j ; Agregue x a la lista L j ; b j = longitud de L j ; repetir h j = función elegida al azar en H sj ; hasta que h j sea inyectivo sobre los elementos de L j ; para todo y en la lista L j, almacene y en la posición h j ( y ) de T j ; fin por fin si no es así FullRehash( x ); fin más fin más fin más fin más fin más fin
La eliminación de x simplemente marca x como eliminado sin eliminación e incrementa el recuento . En el caso de inserciones y eliminaciones, si el recuento alcanza un umbral M , se reconstruye toda la tabla, donde M es un múltiplo constante del tamaño de S al comienzo de una nueva fase . Aquí la fase se refiere al tiempo entre reconstrucciones completas. Tenga en cuenta que aquí el -1 en "Eliminar ( x ) " es una representación de un elemento que no está en el conjunto de todos los elementos posibles U.
la función Eliminar ( x ) es contar = contar + 1; j = h( x ); si la posición h j ( x ) de la subtabla Tj contiene x , marque x como eliminado; terminar si no regresa (x no es miembro de S); terminar si no ( cuenta >= M ) Rehash completo(-1); terminar si terminar
Una reconstrucción completa de la tabla de S primero comienza eliminando todos los elementos marcados como eliminados y luego estableciendo el siguiente valor umbral M en algún múltiplo constante del tamaño de S. Una función hash, que divide S en s ( M ) subconjuntos, donde el tamaño del subconjunto j es s j , se elige repetidamente al azar hasta que:
Finalmente, para cada subtabla T j se elige repetidamente y al azar una función hash h j de H sj hasta que h j sea inyectiva sobre los elementos de T j . El tiempo esperado para una reconstrucción completa de la tabla de S con tamaño n es O( n ). [2]
la función FullRehash( x ) es poner todos los elementos no marcados de T en la lista L ; si ( x está en U ) añadir x a L ; finalizar si cuenta = longitud de la lista L ; M = (1 + c ) * máx{ cuenta , 4}; repetir h = función elegida al azar en H s(M) ; para todo j < s ( M ) formar una lista L j para h( x ) = j ; b j = longitud de L j ; m j = 2 * b j ; s j = 2 * m j * ( m j - 1); finaliza hasta que la suma total de todos los s j ≤ 32 * M 2 / s ( M ) + 4 * M para todos los j < s ( M ) Asigne espacio s j para la subtabla T j ; repetir h j = función elegida al azar en H sj ; hasta que h j sea inyectivo sobre los elementos de la lista L j ; finaliza for for all x en la lista L j almacena x en la posición h j ( x ) de T j ; fin por fin