Se dice que las tablas hash se amortizan usando, por ejemplo, un simple encadenamiento y duplicación a cierta capacidad.
Sin embargo, esto supone que las longitudes de los elementos son constantes. Calcular el hash de un elemento requiere pasar por el elemento, tomar tiempo donde es la longitud.
Pero para discriminar entre elementos, necesitamos que los elementos tengan una longitud de al menos bits; de lo contrario por principio de casillero no serán distintos. La función hash que atraviesa bits de elemento llevará tiempo .
Entonces, ¿podemos decir que la velocidad de una tabla hash, teniendo en cuenta una función hash razonable que utiliza todas las partes de la entrada, es en realidad ? ¿Por qué, entonces, las tablas hash en la práctica son eficientes para almacenar elementos de longitud variable, como cadenas y enteros grandes?
fuente
Respuestas:
La historia de que las tablas hash se amortizanΘ(1) Es
una mentirauna simplificación excesiva.Esto solo es cierto si:k . c . r .
- La cantidad de datos a hash por elemento es trivial en comparación con el número de K eys y la velocidad de hash a K ey es rápida -
- El número de C ollisions es pequeña -
- Nosotros no tomamos en cuenta el tiempo necesario para R edimensionar la tabla hash -
Grandes cadenas de hashΘ(k) . Θ(k) . Entonces, un hash no es asintóticamente más lento, aunque el hash siempre será más lento que una simple comparación, porque la comparación tiene una ergo de exclusión tempranaO(1) , Ω(k) y el hashing siempre tiene que hacer hash con la cadena completa O(k) , Ω(k) .
Si la primera suposición es falsa, el tiempo de ejecución aumentará a
Esto es definitivamente cierto para cadenas grandes, pero para cadenas grandes una comparación simple también tendría un tiempo de ejecución de
Tenga en cuenta que los enteros crecen muy lentamente. 8 bytes pueden almacenar valores de hasta1018 ; 8 bytes es una cantidad trivial de hash.
Si desea almacenar bigints, entonces piense en ellas como cadenas.
Algoritmo de hash lentoΘ(1) la suposición se vuelve insostenible.
Si la cantidad de hashing de gasto no es trivial en comparación con el almacenamiento de los datos, entonces obviamente el
A menos que se use un hash criptográfico, esto no debería ser un problema.
Lo que importa es quen >> k . Mientras eso se mantengaΘ(1) Es una declaración justa.
Muchas colisionesO(log(n)) .
Si la función de hash es deficiente, o la tabla de hash es pequeña, o el tamaño de la tabla de hash es incómoda, las colisiones serán frecuentes y el tiempo de ejecución será de
La función de hashing debe elegirse de modo que las colisiones sean raras y sigan siendo lo más rápido posible, en caso de duda, opte por menos colisiones a expensas de un hashing más lento.
Una regla de oro es que la tabla de hashing siempre debe tener menos del 75% de su capacidad.
Y el tamaño de la tabla hash no debería tener ninguna correlación con la función hashing.
A menudo, el tamaño de la tabla de hash es (relativamente) primo.
Cambiar el tamaño de la tabla hash
Debido a que una tabla hash casi completa generará demasiadas colisiones y una tabla hash grande (vacía) es un desperdicio de espacio, muchas implementaciones permiten que la tabla hash crezca (y se reduzca) según sea necesario.
El crecimiento de una tabla puede implicar una copia completa de todos los elementos (y posiblemente una reorganización), porque el almacenamiento debe ser continuo por razones de rendimiento.
Solo en casos patológicos será un problema cambiar el tamaño de la tabla hash, por lo que los cambios de tamaño (costosos pero raros) se amortizan en muchas llamadas.
Tiempo de ejecuciónΘ(kcr) . k , c , r en promedio se supone que es una constante (pequeña) en el tiempo de ejecución amortizado y, por lo tanto, decimos que Θ(1) Es una declaración justa.
Entonces, el tiempo de ejecución real de una tabla hash es
Cada uno de
Para volver a sus preguntas
Por favor, discúlpeme por parafrasear, he intentado extraer diferentes conjuntos de significados, siéntase libre de comentar si me he perdido algo
Parece que le preocupa la longitud de la salida de la función hash. Llamemos estom (n generalmente se considera que es el número de elementos que se van a codificar). m estarán log(n) porque m necesita identificar de forma exclusiva una entrada en la tabla hash. O(m) (o O(log(n)) Si tu quieres).
Esto significa que m crece muy lentamente. A 64 bits, el número de entradas de la tabla hash ocupará una porción considerable de RAM disponible en todo el mundo. Con 128 bits, superará con creces el almacenamiento en disco disponible en el planeta tierra.
Producir un hash de 128 bits no es mucho más difícil que un hash de 32 bits, así que no , el momento de crear un hash no es
Pero la función hash no pasa por bits de elementos . Por un elemento (!!) solo va a través de los datos . Además, la longitud de la entrada (k) no tiene relación con el número de elementos. Esto es importante, porque algunos algoritmos no hash tienen que examinar muchos elementos en la colección para encontrar un elemento (no) coincidente. La tabla hash solo hace 1 o 2 comparaciones por elemento en consideración en promedio antes de llegar a una conclusión.log(n)
O(k)
Debido a que, independientemente de la longitud de la entrada ( ), la longitud de la salida ( ) es siempre la misma, las colisiones son raras y el tiempo de búsqueda es constante. Sin embargo, cuando la longitud de la clave aumenta en comparación con el número de elementos en la tabla hash ( ), la historia cambia ...k m
k n
Las tablas hash no son muy eficientes para cadenas muy grandes.
Si (es decir, el tamaño de la entrada es bastante grande en comparación con el número de elementos en la tabla hash), entonces ya no podemos decir que el hash tiene un tiempo de ejecución constante, pero debe cambiar a un tiempo de ejecución de especialmente porque no hay salida anticipada. Usted tiene a hash de la clave completa. Si solo está almacenando un número limitado de artículos, entonces puede que sea mucho mejor usar un almacenamiento ordenado, porque al comparar puede optar por salir tan pronto como se vea una diferencia.not n>>k Θ(k) k1 ≠ k2
Sin embargo, si conoce sus datos, puede optar por no usar la clave completa, sino solo la parte volátil (conocida o supuesta) de la misma, restaurando la propiedad mientras mantiene las colisiones bajo control.Θ(1)
Constantes ocultasΘ(1)
Como todos deberían saber simplemente significa que el tiempo por elemento procesado es una constante. Esta constante es bastante más grande para el hash que para una simple comparación. Para tablas pequeñas, una búsqueda binaria será más rápida que una búsqueda hash, porque, por ejemplo, 10 comparaciones binarias podrían ser más rápidas que un solo hash. Para conjuntos de datos pequeños, se deben considerar alternativas a las tablas hash. Es en grandes conjuntos de datos que las tablas hash realmente brillan.
fuente
Comencemos con una pregunta más simple. Considere cuál es quizás la estructura de datos más simple que existe, una matriz . Para concretar, imaginemos una serie de enteros. ¿Cuánto tiempo lleva la operación ? La respuesta depende del modelo de cálculo. Aquí son relevantes dos modelos: el modelo RAM (que es más común) y el modelo de bits (que es más sencillo de explicar).A[i]=A[j]
En el modelo de bits , una operación básica que implica bits de cuesta . Entonces, si los enteros tienen un ancho de bits, la operación costará alrededor de .N N w A[i]=A[j] 2w
En el modelo RAM , la unidad básica de datos no es un bit sino una palabra (a veces conocida como una palabra de máquina ). Una palabra es un entero de ancho , donde es el tamaño de las entradas (en bits). Una operación básica que implica palabras cuesta . En la mayoría de los casos, si tiene una matriz de enteros, los enteros que necesita tienen un ancho , por lo que la operación cuesta .logn n N N O(logn) A[i]=A[j] O(1)
Como dije anteriormente, generalmente analizamos algoritmos usando el modelo RAM. La única excepción común es la aritmética de enteros, especialmente la multiplicación de enteros, que a menudo se analiza con respecto al número de operaciones de bits.
¿Por qué usamos el modelo RAM? Dado que tiene más poder predictivo (frente a la realidad). La suposición de que el tamaño de entrada es como máximo exponencial en el tamaño de una palabra de máquina generalmente está justificada, especialmente para los procesadores modernos de 64 bits, y las operaciones en palabras de máquina toman tiempo constante en las CPU reales.
Las tablas hash son estructuras de datos más complicadas, y realmente involucran tres tipos: el tipo clave, el tipo hash y el tipo de valor. Desde el punto de vista del tipo de valor , una tabla hash es solo una matriz glorificada, así que ignoremos ese aspecto. El tipo de hash siempre se puede suponer que consistir en un pequeño número de palabras de la máquina. El tipo de clave satisface una propiedad especial: es hashable , lo que significa que tiene una operación hash que (como mínimo) es una función determinista (una función que siempre devuelve el mismo valor).
Ahora podemos abordar su pregunta: ¿cuánto tiempo lleva hacer hash una clave? La respuesta depende del modelo de cálculo. Esta vez tenemos tres modelos comunes: los dos anteriores y el modelo oráculo.
En el modelo de oráculo , suponemos que la función hash nos la proporciona un "oráculo" que puede calcular el hash de una clave arbitraria en tiempo constante.
En el modelo RAM y el modelo de bits , la función hash es una función real, y la complejidad temporal de la tabla hash depende de la complejidad temporal de la función hash. Las funciones hash utilizadas para la tabla hash (en lugar de para fines criptográficos) suelen ser muy rápidas y requieren un tiempo lineal en la entrada. Eso significa que si el tipo de clave tiene una longitud de bits (en el modelo de bits) o palabras (en el modelo de RAM), la función hash toma tiempo . Cuando es una constante, la función hash toma tiempo constante.N N O(N) N
Cuando analizamos el tiempo de ejecución de los algoritmos de tabla hash, usualmente utilizamos implícitamente el modelo oracle. Esto a menudo se expresa en un idioma diferente: simplemente decimos que contamos el número de invocaciones de la función hash. Esto tiene sentido, ya que generalmente las aplicaciones de la función hash es el término dominante en el tiempo de ejecución de los algoritmos de la tabla hash, por lo que para analizar la complejidad del tiempo real, todo lo que tiene que hacer es multiplicar el número de invocaciones hash por el tiempo de ejecución de la función hash.
Cuando analizamos el tiempo de ejecución de un algoritmo utilizando una tabla hash como estructura de datos, a menudo nos interesa el tiempo de ejecución real, generalmente en el modelo de RAM. Una opción aquí es hacer lo que se sugirió en el párrafo anterior, es decir, multiplicar el tiempo de ejecución de las operaciones de la tabla hash (dado en términos de número de invocaciones de funciones hash) por el tiempo de ejecución de la función hash.
Sin embargo, esto no es lo suficientemente bueno si las teclas tienen longitudes variables. Por ejemplo, imagine que tenemos claves de tamaño , y calculamos el hash de cada una de ellas. La complejidad de tiempo real es , pero el cálculo anterior solo da . Si este es el caso en alguna aplicación, podemos tenerlo en cuenta ad hoc, utilizando un análisis refinado de la complejidad de la tabla hash subyacente.1,2,4,…,2m O(2m) O(m2m)
fuente