• No results found

A pesar de los argumentos teóricos anteriores, el diseño de funciones hash tiene mucho de tra- bajo artesanal y es una tarea complicada. Por eso, a continuación, se describe una función hash bien documentada en la literatura, que en la práctica ha mostrado ser buena. Puede encontrarse una exposición detallada en [3].

Dicha función hash es un algoritmo iterativo que calcula un valor auxiliar (hi) entre 0 y la

longitud m de la clave id. El valor final h(id) se obtiene a partir de alguno de los bits del va- lor m-ésimo (hm).



h0=0

hi=k×hi-1+ci ∀i 1≤i≤m

h(k)=bits(hm,30)%n

donde

• k es una constante deducida experimentalmente.

• n es el tamaño de la tabla, deducido con k experimentalmente.

• bits(x,j) es una función que obtiene los j bits menos significativos del ente- ro x.

2.3.4.

Factor de carga

Un concepto muy importante en el estudio de la eficiencia es el factor de carga. Dada una tabla hash con espacio para m claves, en la que ya se han insertado n, se llama factor de carga y se re- presenta mediante la letra λ, al cociente entre n y m.

λ = mn

2.3.5.

Solución de las colisiones

Puesto que se van a usar funciones hash que permiten colisiones, es necesario articular mecanis- mos para reaccionar frente a éstas. Aunque son muchas las alternativas posibles, en las siguien- tes secciones se explicarán algunas de ellas con detalle.

2.3.6.

Hash con direccionamiento abierto

Esta técnica recibe su nombre del hecho de que la posición final que se asigna a una clave no está totalmente determinada por la función hash. Lo más característico de este método es que las co- lisiones se solucionan dentro del mismo espacio utilizado por la tabla, es decir, no se usa ningu- na estructura de datos auxiliar para ello. De aquí se deduce la necesidad de que haya siempre posiciones libres en la tabla, es decir, que el tamaño reservado para ella sea siempre mayor que el número de claves que se va a insertar.

La inserción de una clave k mediante direccionamiento abierto funciona de la siguiente ma- nera (la Figura 2.14 muestra el pseudocódigo para la inserción, común a todas las variantes de encadenamiento abierto):

1. Se estima el número de claves que se va insertar en la tabla.

2. Se dimensiona la tabla para que siempre haya posiciones libres. El tamaño necesario de- pende de otros aspectos de la técnica que se explicarán a continuación.

3. Cuando se va a insertar la clave, se calcula la posición que le asignaría la función hash h(k). Si dicha posición está libre, no hay colisión y la información se guarda en esa po- sición. En otro caso hay colisión: se recorre la tabla buscando la primera posición libre (j). La información se guarda en dicha posición j-ésima.

La manera de encontrar la primera posición libre se llama sondeo o rehash. Como se verá a continuación, el sondeo no implica necesariamente que las claves que colisionan en la misma po- sición de la tabla ocupen finalmente posiciones contiguas. Por esta causa, también se conoce al direccionamiento abierto como espaciado. El objetivo del sondeo es ocupar la mayor parte de la tabla con el mejor rendimiento posible.

La recuperación de información de la tabla tiene que tener en cuenta que ya no se garantiza que la información de la clave k esté en la posición h(k). Hay que utilizar el mismo mecanismo de son- deo empleado en la inserción para recorrer la tabla, hasta encontrar la clave buscada. La Figura 2.15 muestra el pseudocódigo de la búsqueda, común a todas las variantes del encadenamiento abierto.

Figura 2.14. Pseudocódigo del algoritmo de inserción de la clave k en la tabla hash T,

común a todas las técnicas con direccionamiento abierto. Se resalta el sondeo.

indice Insertar(clave k, TablaHash T) indice posicion=funcion_hash(k,T); int i=0; /*Numero de reintentos*/

Si k == T.datos[posicion].clave devolver posicion; /*Ya estaba*/

else{

Mientras no vacia(T.datos[posicion]) y no posicion == funcion_hash(k,T) y no k == T.datos[posicion].clave

{posicion = (posicion + delta(i++))mod tamaño(T);}

if vacia(T.datos[posicion]) {/*No estaba y se inserta*/ T.datos[posicion].clave = k;

devolver posición;}

if posicion == funcion_hash(k,T) devolver -1;

/* T no tiene espacio para ese valor de hash */ if k == T.datos[posicion].clave devolver posicion; /*Ya estaba*/

}

Figura 2.15. Pseudocódigo del algoritmo de búsqueda de la clave k en la tabla hash T,

común a todas las técnicas con direccionamiento abierto. Se resalta el sondeo.

indice Buscar(clave k, TablaHash T) indice posicion=funcion_hash(k,T);

Si k == T.datos[posicion].clave devolver posicion; else

{

Mientras no vacia(T.datos[posicion]) y no posicion == funcion_hash(k,T) y no k == T.datos[posicion].clave

{posicion = (posicion + delta(i++))mod tamaño(T);} if vacia(T.datos[posicion]) devolver -1; /*No está*/ if posicion == funcion_hash(k,T) devolver -1;

/* Además esto significa que la tabla no tiene espacio disponible para ese valor de hash */ if k == T.datos[posicion].clave devolver posicion; /*Está*/

Los algoritmos de las Figuras 2.14 y 2.15 muestran el sondeo como un desplazamiento re- presentado por la función delta, que se suma a la posición devuelta por la función hash, en un bucle que recorre la tabla buscando la clave, cuando es necesario. En los próximos párrafos se analizarán diferentes tipos de sondeo, es decir, distintas implementaciones de la función delta.

Obsérvese que de la Figura 2.14 pueden deducirse distintas condiciones para concluir que no hay sitio en la tabla:

• Cuando la tabla está totalmente llena. Ya se ha advertido de la necesidad de que la tabla sea lo suficientemente grande para que esta situación no se produzca nunca.

• Cuando, durante la repetición del sondeo, independientemente de que haya posiciones libres en la tabla, se llega a una posición previamente visitada. En este caso, aunque la tabla tenga sitio, no se va a poder llegar a él.

La segunda condición es muy importante para el diseño del sondeo. Hasta ahora se podía pen- sar que la gestión correcta de todas las claves se garantizaba con una tabla suficientemente gran- de. Sin embargo, un sondeo deficiente, aunque se realice en una tabla muy grande, puede dar lugar a un rendimiento similar al conseguido con una tabla demasiado pequeña. Un ejemplo tri- vial de sondeo deficiente es el que, tras una colisión, sólo visita una única posición más, que es siempre la primera de la tabla.

Como se verá a continuación, esta segunda condición es la que más determina el diseño de los sondeos y el rendimiento del direccionamiento abierto.