Está en la página 1de 14

26/1/2019 Hashing

9. Hashing
9.1 Introducción
El origen de los algoritmos de hash es la ambición de los científicos por encontrar una forma más
rápida de encontrar la información

Tipo de Búsqueda Complejidad


Búsqueda Secuencial O(N)
B-Trees O(logk N)
??? O(1)

9.2 Definición

Una función de Hash es una caja negra que tiene como entrada una llave y como salida una dirección

h(K)=address

Ejemplo: h(LOWELL)=4

Figura 9.1

http://ict.udlap.mx/people/carlos/is215/ir09.html 1/14
26/1/2019 Hashing

El hashing es similar al indexamiento en el sentido de asociación entre llaves y direcciones relativas de


registros
Pero difiere de los índices en 2 cosas:

La dirección generada por Hash suele ser aleatoria (random).

No hay una relación aparente entre la llave y la localización del registro correspondiente

El Hash permite que 2 llaves puedan producir la misma salida --> direcciones iguales, a esto se
le conoce como "colisión". Existen distintos grados de colisiones Figura 9.2

Figura 9.2

9.3 Colisiones

En el ejemplo que mostramos en la sección anterior la función de Hash se basa en lo siguiente:

Se obtiene el código ASCII de las 2 primeras letras de la llave


Se multiplican dichos números
La dirección serán los 3 últimos digitos de dicho resultado (esto para limitar nuestro archivo
únicamente a 1000 direcciones)

Llave ASCII Producto Dirección


BALL 66 65 66x65=4290 290
LOWELL 76 79 76x79=6004 004
TREE 84 82 84x82=6888 888

http://ict.udlap.mx/people/carlos/is215/ir09.html 2/14
26/1/2019 Hashing

A simple vista notamos que nuestra función de hash es pobre ya que fácilmente habrán llaves que
generen la misma dirección ej (OLIVIER), a éstas llaves que generan las mismas direcciones se les
llama "sinónimas".

Idealmente lo que se buscaría es tener un "algoritmo de hash perfecto" en el cual no ocurran


colisiones y siempre nos garantice direcciones diferentes. Pero desafortunadamente esto es casi
imposible, se dice que 1 de 10^120000 algoritmos evitarían dichas colisiones, así que hay que
acostumbrarse a la idea de trabajar y lidiar con colisiones.

Para reducir el número de colisiones se tienen algunas soluciones:

Propagar los registros: Buscar funciones que distribuyan muy aleatoriamente los registros
podemos evitar "agrupaciones" de llaves que produzcan las mismas direcciones

Usar memoria extra: En el ejemplo anterior planteamos tener una dirección de entre 1000
posibles, el uso de memoria extra se basa en proponer un espacio de direcciones posibles
mucho más grande que el número de registros a usar, de modo que si vamos a insertar 100
registros un espacio de 500 direcciones nos una mejor opción de esparcir mejor.

Colocar más de un registro en una dirección: A diferencia de los casos anteriores donde
cada dirección almacena únicamente un registro, este concepto se basa en "buckets" o cubetas
de datos en cada dirección, ahí se colocan algunos (casi todos) los registros que colisionan de
manera que al hacer una búsqueda debemos recuperar la cubeta entera y ahi buscar por el
registro deseado.

9.3.1 Un Algoritmo de Hash

No existe una fórmula "única" para hash, pero el producirla es un algoritmo que
básicamente se presenta en 3 pasos:

1) Representar la llave de manera numérica (siempre que no sea de por sí un


número)
Una buena opción es usar los valores ASCII o bien los Unicode de las letras

LOWELL= L O W E L L _ _ _ _ _ _
76 79 87 69 76 76 32 32 32 32 32 32

2) Plegar y Agregar
Combinar algunos de estos números para generar pequeños trozos con los que podamos
trabajar
76 79 | 87 69 | 76 76 | 32 32 | 32 32 | 32 32
http://ict.udlap.mx/people/carlos/is215/ir09.html 3/14
26/1/2019 Hashing

De manera que podemos hacer algunas operaciones matemáticas con dichos números
para finalmente obtener un número del cual obtendremos la dirección

7679 + 8769 + 7676 + 3232 + 3232 = 30 588

Nota: Respecto a la implementación se puede dar el caso de formar números demasiado


grandes, tanto que llegue al overflow del tipo de datos que estemos usando. Para
solucionar esto podemos usar funciones como el "mod" intermedias para no tener ese
problema.

3) Dividir por un número primo y usar el resultado como dirección


Los archivos de hash por lo general suelen limitarse a un cierto rango de direcciones
posibles para aprovechar mejor el concepto de memoria. de manera que podemos concluir
nuestro algoritmo con la fórmula:

a= s mod n

donde a es la dirección resultante, s es la suma o resultado de los pasos anteriores y n el


número de direcciones posibles en el archivo

Existen innumerables operaciones adicionales que pueden aplicarse en las fórmulas, así
como las técnicas para limitar el valor final. Entre ellas se encuentran: elevar a alguna
potencia, raíz cuadrada, convertir los números de base (hexadecimal, octal) , etc...

9.3.2 Distribución de un archivo de Hash

Continuando con las soluciones para colisiones analicemos el uso de memoria.


Es posible predecir cómo se van a distribuir los registros en un archivo, esto se hace a través de
probabilidades.

La Distribución e Poisson
Probabilidad de que la dirección "b" sea escogida

p(B) = b = 1/ N

N= número de direcciones posibles

Probabilidad de que la dirección no sea escogida "a"

p(A) = a = (N-1)/N = 1 - (1/N)

La probabilidad de que una posición sea escogida 2 veces

http://ict.udlap.mx/people/carlos/is215/ir09.html 4/14
26/1/2019 Hashing

p(BB)= b x b

De manera más general tenemos la función de Poisson para nuestros fines:

La probabilidad de que "x" registros tengan la misma dirección

p(x)= (r/N)^x e^(- r/N)

x!

donde N es el número de direcciones disponibles


r es el número de registros a insertar
x número de registros asignados a alguna dirección

Ejemplo: N=1000 y r= 1000

p(2)= 0.184
p(3)= 0.061
p(4)= 0.015

Por otro lado si tenemos N direcciones, el número de direcciones con x colisiones será:

Np(x)

Densidad del archivo


Para complementar las probabilidades anteriores podemos analizar otro dato más, la densidad del
archivo.

Número de registros = r = densidad del archivo


Número de espacios N

Si tenemos por ejemplo un archivo donde estamos considerando 100 direcciones (espacios) y
tenemos 75 registros entonces la densidad será de:

75 / 100 = 0.75 = 75 %

Este valor no nos indica el porcentaje de espacio en el archivo real, es solo la relación entre el espacio
físico y el de direcciones, mientras más densa sea la relación mayor será el número de
colisiones

http://ict.udlap.mx/people/carlos/is215/ir09.html 5/14
26/1/2019 Hashing

9.3.3 Resolución de Colisiones por el método "Progressive Overflow"

El algoritmo es el más popular y de hecho bastante simple:

Cuando insertamos un registro obtenemos su dirección con la función Hash

Si la dirección esta libre entonces lo insertamos ahí

En caso contrario se busca la primer dirección subsecuente que esté disponible y ahí se inserta

Es importante recordar que nuestro archivo puede (debe en muchos casos) tener un número fijo
de direcciones posibles, de manera que si alcanzamos ese número máximo de direcciones
entonces debemos buscar un espacio disponible desde el inicio del archivo. Si no queda ninguno
entonces quizás tuvimos un error en nuestros cálculos y planeación del archivo.

Nota: con espacio disponible nos referimos a un espacio "reutilizado" o bien a un espacio vacío,
considerando que al eliminar un registro lo que hacemos es reutilizar su espacio.

Figura 9.3

Para las búsquedas

Se obtiene el hash correspondiente

A parte de esa dirección empezamos a buscar por el registro deseado o un espacio vacío

http://ict.udlap.mx/people/carlos/is215/ir09.html 6/14
26/1/2019 Hashing

En caso de encontrar un espacio totalmente vacío (nuevo, no reutilizable) entonces podemos


afirmar que el registro no se encuentra en nuestro archivo.

Si se alcanza el final del archivo y no se encuentra el registro buscado ni un espacio vacío


entonces seguimos buscando desde el inicio del archivo hasta volver al punto de partida
(dirección original del hash).

Figura 9.4

9.3.4 Almacenando más de un registro por dirección (Buckets)

La idea es muy similar a la que se presentó en la sección anterior, sólo existen algunas diferencias
notables:

Cada dirección representa una agrupación de datos (cubeta)


Aquí el porcentaje de colisiones es mayor, así debe ser para aprovechar el concepto de
agrupación
Cuando una cubeta se llena (Figura 9.5) entonces tenemos un overflow y debemos buscar una cubeta
disponible para seguir insertando. Afortunadamente este caso no sucede muy a menudo

Green... Hall...

Jenks...
King... Land... Marks...
Figura 9.5

http://ict.udlap.mx/people/carlos/is215/ir09.html 7/14
26/1/2019 Hashing

Distribución en el almacenamiento por cubetas

Respecto a las fórmulas de distribución la expresión de Poisson permanece intacta ya que el cálculo
es igual solo que aquí nos interesa tener un mayor número de colisiones por dirección.

Por otro lado para la densidad si tendremos una ligera variación, de manera que el cálculo ahora se
obtendrá como:

r = Número de registros
bN Registros por cubeta X Número de espacios o direcciones

9.3.5 Otras soluciones para las Colisiones

a) Double Hashing
Esta técnica se basa en la idea de generar otro hash, quizás con un formula diferente.
De manera que si el hash de una llave nos da por ejemplo 5 y ya existe un registro en esa posición
entonces se vuelve a aplicar otro hash que nos de una dirección más dispersa por ejemplo 505 y de
alguna manera nos garantice menos colisiones. Es importante aclarar que este método no eliminar las
colisiones solo no ayuda a encontrar más rapidamente un registro en un espacio donde de antemano
sabemos que existe este problema.

b) Chained Progressive Overflow


La idea, como se muestra en la figura 9.6, es muy simple y se basa en crear una lista ligada entre los
distintos registros "sinónimos" de manera que nos podemos desplazar entre aquellos que colisionan
de una manera más ágil y natural.

http://ict.udlap.mx/people/carlos/is215/ir09.html 8/14
26/1/2019 Hashing

Figura 9.6

c) Chaining with Separate Overflow Area


Aquí se emplea nuevamente el concepto de encadenamiento a través de una lista ligada, con la
diferencia que aquellos registros "sinónimos" que colisionan se van almacenando en un archivo aparte
llamado "Overflow Area" (figura 9.7), de manera que en el archivo de datos (Primary Data Area) sólo
se almacena la cabeza de dicha lista.

Figura 9.7

d) Scatter Tables
La idea es parecida a la anterior sólo que aquí no existe un área primaria de datos, sólo apuntadores
hacia distintas cabeceras de listas donde residen aquellas llaves "sinónimas" que producen la misma
dirección (figura 9.8). Los detalles de implementación pueden variar, desde algunas no muy
convenientes como el usar muchos archivos para las diversas listas o bien el implementar en un solo
archivo una "lista de listas".

Figura 9.8

9.4 Hash Extendido


http://ict.udlap.mx/people/carlos/is215/ir09.html 9/14
26/1/2019 Hashing

En la sección 9.3 se mencionaron varias técnicas relacionadas con Hash y el manejo de colisiones,
dichas técnicas se basan en una estructura estática, es decir, se calcula un cierto número de
posiciones posibles (registros) y en base a este supuesto se amplía el espacio de direcciones y en
consecuencia se disminuyen las colisiones.
Desafortunadamente este esquema "estático" no es muy adecuado para la mayoría de las
aplicaciones ya que normalmente no se tiene un estimado del número de registros que se
almacenarán, de manera que se requiere una estructura también basada en Hash pero que sea más
dinámica de modo que permita "crecer" y adecuarse a cualquier número de elementos; así surge el
Hash Extendido.

El algoritmo de Hash Extendido se basa en una estructura de recuperación conocida como "trie", la
cual se encuentra dentro del grupo de búsquedas por radio, esto debido a que se presenta un árbol
donde cada rama presenta un radio de posibilidades hacia donde moverse. en la figura 9.9 tenemos
un radio de 26 que son las letras del alfabeto.

Figura 9.9

De manera que el "radio" definirá el número de ramas posibles desde cada nodo, en la figura 9.10 el
radio es igual a 10 ya que únicamente emplea números.

http://ict.udlap.mx/people/carlos/is215/ir09.html 10/14
26/1/2019 Hashing

Figura 9.10

Para el caso del hash extendido, el trie que se emplea es de radio 2, utilizando como valores posibles
el 1 y el 0, significando un bit. De manera que las decisiones de "a qué rama moverse" se definirá
como una decisión bit por bit. Así, si buscamos alguna llave cuyo hash produce una dirección empieza
con "11" entonces recuperaremos la "cubeta C".

Figura 9.11

Como se puede observar le "trie" es una estructura en forma de árbol, pero si tratamos de manejarlo
como tal tendremos un caso similar al indexamiento tradicional resultando la mejor solución en un B-
Tree. Así que para poder aprovechar los beneficios del trie lo que se hace es representarlo a manera
de arreglo de registros (cubetas en la mayoría de los casos) contiguos, al cual se le conoce como
"directorio".
Para convertir el trie a directorio lo primero que se debe hacer es representar el árbol como una
estructura completa donde existan al mismo nivel el máximo número de hojas posible (Figura 9.12a),
posteriormente elaboramos el arreglo en un archivo de manera que utilicemos todo el rango de
direcciones posibles (00,01,10,11) donde cada dirección contendrá un apuntador a la cubeta
correspondiente.

http://ict.udlap.mx/people/carlos/is215/ir09.html 11/14
26/1/2019 Hashing

Figura 9.12

Manejando el overflow
La gran ventaja de esta estructura es que permite de una manera muy sencilla manejar el overflow
en las cubetas, supongamos que existe un overflow en "A", gracias a que de antemano teníamos
reservada una dirección extra (la rama adicional del árbol) simplemente usamos esa dirección para
apuntar hacia la nueva cubeta "D".

Figura 9.13

Por otro lado supongamos que el overflow ahora ocurre en "B", a diferencia del ejemplo anterior aquí
no tenemos una dirección extra para relacionar a la nueva cubeta "D". De manera que dinámicamente
el trie deberá crecer ahora a 3 bits de profundidad (figura 9.14a).
Nuevamente completamos el árbol de 3 niveles para tener todas las hojas completas (figura 9.14b) y
para formar el directorio debemos hacer un "ajuste" de manera que ahora el rango de direcciones irá
de 000 hasta 111 (Figura 9.14c).

http://ict.udlap.mx/people/carlos/is215/ir09.html 12/14
26/1/2019 Hashing

Figura 9.14

En esta sección se omiten los detalles de implementación, pero cabe mencionar algunos de ellos:

Las direcciones obtenidas por el hash suelen leerse de derecha a izquierda, ejemplo:

Supongamos que vamos a ingresar la llave 45 y su hash nos da 000 0010 1110 0011,
naturalmente a la izquierda siempre tendremos muchos 0s así que tendríamos muchas
colisiones para esos valores así que se opta por empezar a la derecha, de modo que
tendríamos 1100 0111 0100 0000, y por lo tanto la llave se almacenaría en la cubeta "C"

Para la eliminación lo que se debe hacer es "colapsar" el directorio de registros, buscando


aquellas cubetas que sean vecinas.

Concluyendo:

http://ict.udlap.mx/people/carlos/is215/ir09.html 13/14
26/1/2019 Hashing

El concepto de Hash es un caso ideal en la recuperación de información O(1)

El Hash tradicional es muy eficiente pero tiene 2 problemas principales: colisiones y el hecho de
ser estático

El Hash extendido es una estructura relativamente moderna que promete ser mejor que el B-
Tree, desafortunadamente existen algunas deficiencias que lo limitan, entre ellas:

No se tienen algunas métricas importantes acerca de la capacidad de cada cubeta

Al igual que el BTree la estructura crece dinámicamente y permite búsquedas de alta


velocidad; pero para el acceso secuencial (B+Tree) es una ventaja que sigue ganando la
competencia.

http://ict.udlap.mx/people/carlos/is215/ir09.html 14/14

También podría gustarte