Tema 8: Tabla Hash

Anuncio
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Apuntes elaborados por: Eduardo Quevedo, Aaron Asencio y Raquel López
Revisado por: Javier Miranda el ????
Tema 8: Tabla Hash
Las tabla hash aparece para conseguir una búsqueda e inserción muy rápidas; para ello se hace uso de un array, con lo que volvemos a la estructura básica
de almacenamiento. Sin embargo, hay una diferencia importante que es lo que
hace que sea mejor que el array en cuanto a rapidez: a cada dato se le asigna,
mediante una fórmula matemática denominada función hash, una posición única
en la tabla, con lo que la búsqueda, la inserción y el borrado son inmediatos: O(1).
Como ocurre siempre, lo que ganamos por un lado, lo perdemos por otro;
en el caso de la tabla hash los inconvenientes son:
⇒ Al tratarse de un array, el tamaño de la tabla está limitado y debe fijarse
desde el principio.
⇒ Como las posiciones ocupadas no tienen por qué ser consecutivas, no se
puede recorrer el contenido de una tabla hash.
⇒ Como la posición de una palabra se calcula de forma matemática, los datos
no pueden almacenarse ordenados.
⇒ Si permitimos datos duplicados se produce lo que se denomina “colisión”,
que consiste en que a dos palabras se les asigna la misma posición en el
array. Este es un problema que trataremos de resolver.
Función hash:
Hasta ahora hemos dicho que a cada palabra se le asigna una determinada posición de la tabla por medio de la función hash, pero todavía no hemos definido esta
función. Veamos una primera solución y sus inconvenientes, y las soluciones que
nacen a partir de ésta:
Solución 1:
Proponemos como una primera función hash la suma del código ASCII de
cada una de las letras de la palabra:
F ( Palabra) =
Palabra ' Last
∑ Character' Pos( Palabra(i))
i = Palabra ' First
Por ejemplo: F (" alfredo" ) = 97 + 108 + 102 + 114 + 101 + 100 + 111 = 733
Problemas:
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Colisión 1 : Palabras idénticas.
Colisión 2 : Palabras formadas por las mismas letras pero en distinto
orden.
Colisión 3 : Palabras distintas cuyas letras dan la misma suma.
La suma da números muy pequeños.
Solución 2: Suma ponderada o polinómica.
Cambiamos la función hash para que el resultado sea mayor; para conseguir
esto multiplicamos la posición de cada letra de la palabra por el número de letras del alfabeto (27) elevado al peso que tiene la letra en la palabra:
F ( Palabra ) = Polinomio( Palabra ) = n.27 n + (n − 1).27 n −1 + ... + 2.27 2 + 1.271
Por ejemplo: F (" alfredo" ) = 7.27 7 + 6.27 6 + 5.27 5 + 4.27 4 + 3.27 3 + 2.27 2 + 1.271
Problemas:
Los resultados de la función hash salen muy diferentes para cada
palabra, con lo que éstas se guardan en posiciones muy separadas
de la tabla, desperdiciando de esta forma una gran cantidad de memoria.
Si introducimos una palabra muy grande, la posición resultante es
demasiado elevada. Por ejemplo, si queremos insertar una palabra
de 10 letras, la posición que le corresponde viene dada por
10.2710 + 9.27 9 + ... + 2.27 2 + 1.271 .
Solución 3:
Para evitar que se produzcan resultados demasiado elevados, dividimos el resultado de la función de la solución anterior entre el tamaño de la tabla hash y
tomamos como resultado de la nueva función hash el resto de esta división.
F ( Palabra) = Polinomio( Palabra) mod Tamaño _ Tabla
Problemas:
Se producen muchas colisiones debido a la reducción del resultado
de la función hash con el “mod”.
Solución 4:
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Para arreglar el problema de las colisiones hay dos posibles opciones:
9 Usar memoria dinámica: Cada posición de la tabla hash contiene una
lista o un árbol. De esta forma, si a dos palabras les corresponde la
misma posición de la tabla, lo único que hay que hacer es insertar ambas en la lista o en el árbol, según sea el caso.
9 Si la solución anterior no es viable, hay que buscar otra manera de arreglar las colisiones. Esta solución consistiría en buscar, mediante saltos,
otra posición de inserción de la palabra a partir de la posición que le corresponde según la función hash, sólo en el caso de que esta posición
esté ocupada, es decir, sólo si se produce una colisión. Hay varias implementaciones de esta solución en función del salto que se utilice:
1. Salto = Posición + n, con n = 1, 2, ... De esta forma insertaremos
la nueva palabra en la posición no ocupada más próxima a partir
de la posición que le correspondería en realidad. Por ejemplo:
Queremos insertar
“72”, pero la posición que le corresponde (posición 2)
ya está ocupada,
0
1
2
52
3
4
5
6
7
67
8
28
por lo que insertaremos “72” en la
posición
vacía
más próxima a 2 a
partir de ésta
0
1
2
52
3
72
4
102
5
83
6
7
67
8
28
9
Problemas:
0
1
2
52
3
72
De la misma
forma que antes,
vamos a insertar
“102”
1
2
52
3
72
4
4
102
5
5
6
6
7
67
7
67
8
28
8
28
9
9
Ahora queremos
insertar “83”, pero
la posición que le
corresponde (posición 3) ya está
ocupada.
0
9
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Las palabras tienden a concentrarse en ciertas zonas de la
tabla, dejando grandes espacios en blanco entre cada grupo
de posiciones ocupadas.
2. Salto = Posición + n2, con n = 1, 2, ... De esta forma, evitamos el
apelotonamiento de datos.
Problemas:
Si queremos insertar una palabra y justo las posiciones x +
12, x + 22, x + 32, ... están ocupadas, mientras que las que
quedan en medio están vacías, va a tardar demasiado en
encontrar un hueco libre en la tabla cuando en realidad se
ha pasado por alto unos cuantos.
Queremos insertar “12”, pero la posición que le corresponde (posición 2) ya está ocupada Æ buscamos la
posición de inserción:
0
1
2
52
3
72
4
5
6
7
8
102
x + 1 = 3 Æ Ya está ocupada.
x + 4 = 6 Æ Ya está ocupada.
x + 9 = 11 Æ Se sale de la tabla, con lo que contamos a partir del principio: 11 – 9 = 2
Æ Ya está ocupada.
... Así entraríamos en un bucle sin fin, pues las posiciones se van a repetir y, sin embargo, todavía nos
quedan 7 posiciones libres en la tabla.
9
3. Salto variable: el valor de este salto no puede ser aleatorio, ya
que de ser así, una vez insertada una palabra no seríamos capaces de encontrarla en la tabla, pues no sabríamos cuánto hay que
saltar a partir de su posición resultante de la función hash. Una
posible solución es calcular este salto por medio de otra función
hash que resulte un número más pequeño:
Salto = k − (( Polinomio( Palabra )) mod k )
donde k es el máximo salto que permitimos (lo elegimos nosotros).
Algoritmos en Ada:
function Posición_Hash ( Palabra : in String;
Tamaño_Tabla : in Positive)
return Natural is
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Posición : Natural := 0;
begin
for I in Palabra’Range loop
Posición := (Posición + (I * Character’Pos(Palabra(I)
**I))) mod Tamaño_Tabla;
end loop;
return Posición;
end Posición_Hash;
function salto_Hash (Palabra : in String) return Integer is
begin
return Character’Pos (Palabra(I)) mod 15;
end Salto_Hash;
Observaciones importantes:
ª Se recomienda fijar el tamaño de la tabla hash como el doble del necesario para almacenar todos los elementos.
ª Si queremos ampliar el tamaño de la tabla, como la función hash depende del tamaño de ésta, hay que recalcular las posiciones de todos lo
elementos e insertarlos en estas nuevas posiciones (NO se puede copiar
directamente el contenido de una tabla a otra).
ª El tamaño de la tabla debe ser un número primo para evitar bucles infinitos como ocurría en el ejemplo del segundo tipo de salto.
ª Se recomienda no permitir datos duplicados a no ser que vayamos a
implementar la tabla hash con listas, ya que si se almacena todo en el
array la gestión de la información para insertar y buscar se complica.
ª En caso de que permitiéramos datos duplicados en una tabla implementada con saltos, necesitaríamos introducir un nuevo campo “Borrado” de
tipo boolean en cada posición que nos diga si el dato contenido en esa
posición ha sido borrado o todavía no se ha insertado nada en ella.
Observación. Esto es así porque, al haber datos duplicados, el segundo elemento que insertemos se irá a colocar en una posición de la tabla
que no se corresponde con el resultado de la primera función hash. En
caso de que no usásemos la variable boolean, si borrásemos el elemento que había en la primera posición, cuando intentásemos buscar el segundo elemento se elevaría la excepción No_Encontrado pues la posición que le corresponde está vacía. Sin embargo, con el boolean, como
al borrar el primer elemento habríamos puesto esta variable a True, al
buscar el segundo elemento basta con seguir calculando saltos hasta
Programación. Tema 8: Tablas Hash
16/Mayo/2004
que lo encontremos, hasta que en una posición en la que debería estar
el elemento el campo “Borrado” esté puesto a False o hasta que la posición calculada con saltos sea de nuevo igual a la posición de la que partimos.
Lo vemos mejor con un ejemplo:
Supongamos un array implementado con el primer tipo de salto, es
decir, saltos a posiciones consecutivas:
0
1
2
3
4
5
6
7
8
9
Dato
:0
Borrado : False
Dato
: 31
Borrado : False
Dato
: 12
Borrado : False
Dato
: 32
Borrado : False
Dato
: 44
Borrado : False
Dato
: 15
Borrado : False
Dato
: 42
Borrado : False
Dato
: 12
Borrado : False
Dato
:0
Borrado : False
Dato
:0
Borrado : False
0
1
Borramos “12”
2
3
4
5
6
7
8
9
Dato
:0
Borrado : False
Dato
: 31
Borrado : False
Dato
:0
Borrado : True
Dato
: 32
Borrado : False
Dato
: 44
Borrado : False
Dato
: 15
Borrado : False
Dato
: 42
Borrado : False
Dato
: 12
Borrado : False
Dato
:0
Borrado : False
Dato
:0
Borrado : False
Si ahora quisiéramos buscar el elemento “12” los pasos que seguiríamos serían:
1. Posición (12) = 12 mod 10 = 2
2. Comprobamos el campo “Borrado” de la posición 2 de la tabla.
Como está puesto a True, seguimos buscando.
3. Comprobamos el “Borrado” de la posición 3. Como está a False, miramos a ver si el dato coincide con el que estamos buscando. Como no es así, seguimos buscando.
....
7. Comprobamos el “Borrado” de la posición 7. Como está a False, miramos a ver si el dato coincide con el que estamos buscando. Como esto último se cumple, retornamos True.
Programación. Tema 8: Tablas Hash
16/Mayo/2004
Descargar