34
9: Hash 1 ELO320 Estructuras de Datos y Algoritmos Hashing Tomás Arredondo Vidal Este material está basado en: Robert Sedgewick, "Algorithms in C", (third edition), Addison-Wesley, ISBN 0-201-31663-3, 2001. B. Kernighan, R. Ritchie, “The C Programming Language”, Prentice Hall, ISBM 0-13-110362-8, 1988. material en el sitio http://www.wikipedia.org material del curso ELO320 del Prof. Leopoldo Silva

ELO320 Estructuras de Datos y Algoritmos Hashing ...profesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/ELO-320... · para arreglos asociativos, indexación de bases de datos

  • Upload
    ngotruc

  • View
    225

  • Download
    0

Embed Size (px)

Citation preview

9: Hash 1

ELO320 Estructuras de Datos y Algoritmos

Hashing

Tomás Arredondo Vidal

Este material está basado en:

❒ Robert Sedgewick, "Algorithms in C", (third edition), Addison-Wesley, ISBN 0-201-31663-3, 2001.

❒B. Kernighan, R. Ritchie, “The C Programming Language”, Prentice Hall, ISBM 0-13-110362-8, 1988.

❒material en el sitio http://www.wikipedia.org

❒material del curso ELO320 del Prof. Leopoldo Silva

9: Hash 2

9-Hash

9.1 Definiciones

9.2 Funciones de Hash

9.3 Tablas de acceso directo

9.4 Resolución de colisiones

9: Hash 3

Definiciones

❒ Una tabla de hash o un mapa de hash es una estructura de datos que usa una función de hash para asociar identificadores llamados claves o keys (e.g. el nombre de una persona) a un elemento asociado (e.g. su numero telefónico).

❒ Una función de hash se utiliza para asociar una clave a un índice (i.e. el valor de hash) de un elemento (i.e. slot o balde) que se almacena en la tabla de hash (i.e. un arreglo)

índices

claves

función de hash elementos

Fuente: http://en.wikipedia.org/wiki/Hash_table

9: Hash 4

Definiciones II

❒ Idealmente, la función de hash debería asignar cada posible clave a un índice único pero esto es raramente factible en la practica (generalmente el numero de claves posibles es mucho mayor que el tamaño de la tabla y el numero de índices disponibles).

❒ La mayoría de los diseños de tablas hash asumen que las colisiones van a ocurrir (la situación en la cual a dos claves distintas se le asigna el mismo índice por la función de hash).

❒ En buenas implementaciones el costo promedio para cada búsqueda es independiente del numero de elementos almacenado y en un costo constante.

❒ En muchas situaciones, las tablas de hash son mas eficientes que la búsqueda en árboles u otros métodos de búsqueda. Por eso es que son ampliamente utilizados en aplicaciones para arreglos asociativos, indexación de bases de datos y caches.

9: Hash 5

9-Hash

9.1 Definiciones

9.2 Funciones de Hash

9.3 Tablas de acceso directo

9.4 Resolución de colisiones

9: Hash 6

Funciones de Hash

❒ La función de hash debe transformar claves en índices. Esta operación aritmética es típicamente simple de implementar pero hay que evitar problemas que pueden ocurrir al generar los índices.

❒ Si se toma una tabla que puede tener B elementos, la función de hash h(x) debe transformar la clave en enteros en el rango [0, B-1].

❒ Una función ideal de hash es simple y aproxima a una función aleatoria. Para cada input cada posible output debe ser visto como igualmente probable (sin patrones discernibles).

❒ La función de hash a usar depende del tipo de clave y la implementación eficiente es muchas veces dependiente de las instrucciones y formatos de números disponibles en el procesador.

9: Hash 7

Funciones de Hash II

❒ La función de hash muele o desmenuza o hace un picadillo la clave, de allí su nombre.

❒ La función h(x) debe distribuir las claves, lo más equitativamente posible, entre los B valores.

❒ Si h(xi)=h(xj) se dice que hay una colisión y debe haber un método para resolverla.

❒ Si la función de hash es “buena”, habrán pocas colisiones y la probabilidad de colisión de claves distintas será 1/B. La complejidad para insertar, buscar y eliminar elementos seria O(1).

❒ Si todas las claves que se buscan en la tabla, producen colisiones, habría una probabilidad de colisión de 1, que es el peor caso. La complejidad en el peor caso seria O(n).

9: Hash 8

Función de hash para flotantes

❒ Un ejemplo simple es una función de hash que transforma números flotantes (las claves) a enteros (los índices).

❒ Por ejemplo si se tienen claves entre 0 < x < 1.0 y una tabla detamaño B=97. Entonces se podrían generar claves de manera simple pero no muy buena al multiplicar las clave por B.

❒ Dado ciertas claves se generaría una lista de índices como la siguiente:

.51234234 51

.17298230 17

.30898293 30

…etc…

❒ Como se puede apreciar los bits mas significativos del numero flotante determinan el valor de hash, los bits menos significativos no juegan ningún rol. Un objetivo de una función de hash es evitar estos desbalances para que cada bit en la clave juegue un rol en el calculo del índice.

9: Hash 9

Función de hash para enteros

❒ Una función bastante simple es elejir un número B primo cercano al número de baldes deseado, y luego sacar módulo B.

int hash(int clave)

{ //conviene escoger B un numero primo

return clave%B;

}

❒ Si B es una potencia de dos (e.g. 64), la división se efectuará mediante corrimientos a la derecha, con lo cual el valor de hashno será afectado por la adición de múltiples de B. El resultado sólo dependerá de los bits menos significativos de la clave (genera muchas colisiones), mejor usar un valor de B primo.

❒ Mientras más bits de la clave participen en la formación del valor, mejor será la función de hash.

❒ Estas funciones se llaman modular hash functions o funciones modulares de hash.

9: Hash 10

Función de hash para enteros II// La siguiente definiciones de tipos son// para máquinas de 16 bits (e.g. microcontroladores)typedef unsigned long int u32; //32 bitstypedef unsigned int u16; //16 bits/* Mezcla los números de la clave, asumiendo enteros de 32 bits. Robert Jenkin

*/u16 inthash(u32 key){

key += (key << 12);key ^= (key >> 22);key += (key << 4);key ^= (key >> 9);key += (key << 10);key ^= (key >> 2);key += (key << 7);key ^= (key >> 12);return (u16) key%B;

}

9: Hash 11

Función de hash para strings

❒ Una manera simple para hacer un hash de codificar strings es tomar cada carácter del string (i.e. la clave) como un número en base 128. Esto pondera cada carácter de manera distinta que es importante para evitar que permutaciones de palabras tengan el mismo valor (e.g. “hola” y “aloh”).

❒ Entonces por ejemplo para palabras de tres letras se puede convertir “now” en:110 · 1282 + 111 · 1281 + 119 · 1280 = 1816567

❒ Si se usa un modulo B=64 entonces se considerarían los 6 bits menos significativos del resultado: 1816567 % 64 = 55

❒ Esto causaría muchas colisiones por ejemplo con la palabra “few”, de hecho todas las palabras de tres letras que terminan en “y” generan el índice 57.

❒ Se generan menos colisiones (incluso usando una tabla más pequeña) con el modulo 31 (que es un numero primo) [Sedgewick14.1].

9: Hash 12

Función de hash para strings II

❒ Existen listas de números primos por lo que se podría elegir la tabla de acuerdo al primo mas cercano que satisfaga el número de elementos a almacenar.

❒ Una buena función, propuesta por Kernighan y Ritchie (K & R):

unsigned int stringhash(char *s)

{

unsigned int h=0;

for( ; *s; s++ )

h = 131*h + *s; // nótese 131 es un número primo

return (h%B);

}

9: Hash 13

Funciónes de hash para strings III

❒ Otra función, propuesta por Sedgewick [Sedgewick 14.1]:

unsigned int hash(char *v, int B)

{

unsigned int h, a=127;

for( h=0; *v; v++ )

h = (a*h + *v) % B;

return h;

}

❒ El valor de a primo permite evitar anomalías si es que la tabla usada (B) es de tamaño múltiple de 2.

9: Hash 14

Funciónes de hash para strings IV

❒ Otra función, propuesta por Sedgewick [Sedgewick 14.1]:

unsigned int hash(char *v, int B)

{

unsigned int h, a=31415, b=27183;

for( h=0 ; *v ; v++, a = a*b % (B-1) )

h = (a*h + *v) % B;

return h;

}

❒ Similar al anterior pero usa coeficientes pseudoaleatorios en vez de una base (o radix) fija. Esto para aproximar el ideal de tener una probabilidad de colisiones entre claves no ideales de 1/B.

9: Hash 15

Funciónes de hash para strings V

// La siguiente función propuesta por Serge Vakulenko,// genera mediante dos rotaciones y una resta, por cada// carácter del string.

unsigned int ROT13Hash (char *s){

unsigned int hash = 0;unsigned int i = 0;for (i = 0; *s; s++, i++){

hash += (unsigned char)(*s);hash -= (hash << 13) | (hash >> 19);

}return hash%B;

}

9: Hash 16

MD5

❒ MD5 es un algoritmo de digestión de mensajes diseñado en 1991 por Ronald Rivest del MIT.

❒ Su uso principal es generar una firma digital de 128 bits (fingerprint) a partir de un documento de largo arbitrario. La cual aplicada a strings podría emplearse como función de hash, seleccionando algunos de los bits de los 128.

❒ MD5 es un acrónimo de Message Digest Algorithm 5, derivado de versiones anteriores (MD4, MD3, etc.).

❒ La digestión del mensaje debe entenderse como la generación

de un resumen de 128 bits de éste.

9: Hash 17

MD5 (cont)

❒ Está basado en el supuesto de que es poco probable que dos mensajes diferentes tengan la misma firma digital, y menos probable aún producir el mensaje original a partir del conocimiento de la firma.

❒ Se usa para verificar si un determinado archivo ha sido modificado, pudiendo ser éste un correo, imagen, o un programa ejecutable.

❒ El originador del documento puede establecer cuál es su firma digital, y el usuario debería comprobar que su copia tiene la misma huella digital, regenerándola localmente y comparándola con la de la distribución.

❒ Una función de hash es irreversible, si no existe algoritmo que, ejecutado en un tiempo razonable, permita recuperar la cadena original a partir de su valor de hash.

9: Hash 18

9-Hash

9.1 Definiciones

9.2 Funciones de Hash

9.3 Tablas de acceso directo

9.4 Resolución de colisiones

9: Hash 19

Tablas de acceso directo❒ Un arreglo es una estructura de datos que permite implementar tablas de acceso

directo. ❒ Es este caso la clave es el índice del arreglo y existe un índice (posición del arreglo)

para cada clave posible. El numero de elementos almacenados debe ser menor o igual al numero de baldes (0 ≤ n ≤ B).

❒ El contenido de una celda del arreglo puede ser directamente el elemento almacenado o un puntero al elemento con los datos asociados a este, en ambos casos se pueden implementar las operaciones en forma sencilla.

❒ Si el elemento asociado a una clave no está presente se lo puede indicar con un puntero de valor NULL o simplemente se puede tener en el elemento un campo indicando esta condición.

❒ La inserción de un elemento falla si ya hay un elemento en la posición del arreglo dada por la función de hash para su clave (i.e. no se permiten colisiones o múltiples elementos en los que la función de hash produzca el mismo índice dado sus claves).

índicesclaves

función de hash elementos

9: Hash 20

Tablas de acceso directo// Ejemplo usando punterospnodo buscar(int clave){

return (Tabla[clave]);}int insertar(int clave, pnodo

pestructura){

if (Tabla[clave] == NULL ){

Tabla[clave]=pestructura;return 0;

}else

return (1); // error: ya estaba.}

/* Todas las operaciones son O(1).Para emplear esta solución el tamaño del arreglo, debe ser igual al número de claves posibles y éstas deben estar entre 0 y B-1.*/

int descartar(int clave){

if (Tabla[clave]!= NULL ){

free(Tabla[clave]);Tabla[clave]=NULL ;return 0;

}else

return (1); //error: no estaba.}

9: Hash 21

9-Hash

9.1 Definiciones

9.2 Funciones de Hash

9.3 Tablas de acceso directo

9.4 Resolución de colisiones

9: Hash 22

Estrategia de hash abierto❒ Usando la estrategia de hash abierto (también llamadas de encadenamiento

o chaining) se resuelven colisiones vía estructuras de elementos (e.g. listas de elementos o árboles de elementos).

❒ En estas tablas el número de elementos almacenados (n) puede ser mayor que el numero de baldes (B).

❒ Ejemplo: usando listas se tiene que h(“John Smith”) = h(“Sandra Dee”) = 152

índicesclaves

función de hash

listas de elementos

9: Hash 23

Tabla de hash abierto usando listas❒ Un ejemplo usando nombre como clave, en esta celda se podría

guardar cualquier información adicional de utilidad (e.g. telefono).typedef struct moldecelda{

char *nombre;struct moldecelda *next;

} tcelda, *pcelda;#define B 11 /* 11 baldes, un valor primo */static pcelda hashtabla[B]; /* tabla punteros */

// La clave en la celda es un string dinámico.void makenull(void){

int i;for ( i = 0; i < B; i++)

hashtabla[i] = NULL;}

9: Hash 24

Tabla de hash abierto usando listas: buscar

❒ Asume el uso de una funcion de hash de strings a enteros como las indicadas previamente: int h(char *s)

❒ Sirve para buscar si una clave está en la tabla.

Calcula el índice;Mientras la lista no haya llegado al final:

Si la clave del nodo es igual a la buscada: retorna puntero al nodo.

Si llegó al final: retorna nulo, que implica que no encontró la clave.

9: Hash 25

Tabla de hash abierto usando listas

pcelda buscar(char *s){

pcelda cp; /* current pointer */for (cp = hashtabla[h(s)]; cp!= NULL; cp = cp->next)

if (strcmp(s, cp->nombre ) == 0)return (cp); /* lo encontró */

return (NULL);}

// Esta funcion es para crear un string dinamico [K & R]*/char *strsave(char *s) {

char *p;if (( p = malloc(strlen(s) + 1)) != NULL)

strcpy(p, s);return (p);

}

9: Hash 26

Tablas de hash abierto usando listas: insertar

❒ Insertar elemento en la tabla.

Buscar clave en la tabla;Si la encontró:

Retorna NULL. Es error de inserción en conjuntos.Si no la encuentra:

Crea nodo. Si no puede crear el nodo, retorna NULL.

Si pudo crear nodo. Crea string dinámico (clave). Si crea el string.

Asocia el string con el nodo. Si no puede crear el string, retorna NULL.

Ubica el balde para insertar. Insertar al inicio de la lista. Retorna puntero al insertado. Operación exitosa.

9: Hash 27

Tablas de hash abierto usando listas: insertarpcelda insertar(char *s){

pcelda cp;int hval;if (( cp = buscar(s)) == NULL){

cp = (pcelda ) malloc(sizeof (tcelda ));if (cp == NULL)

return (NULL);if (( cp-> nombre = strsave(s)) == NULL )

return (NULL);hval = h(cp -> nombre);cp -> next = hashtabla[hval];hashtabla[hval] = cp;return (cp);

}else

return (NULL);}

9: Hash 28

Tablas de hash abierto usando listas: descartar

Cálcula índice. Si no hay elementos ligados al balde:

retorna 1. Si hay elementos:

Busca en el primer elemento, si lo encuentra liga la lista. Si no es el primer elemento recorre la lista, manteniendo unpuntero q al anterior. Si lo encuentra a partir del segundo: Pega la lista, mediante q. Si estaba en la lista: Liberar espacio; retorna 0, indicando función realizada. Si no la encontró: retorna 1, error de descarte.

9: Hash 29

int descartar(char *s){

pcelda q, cp = hashtabla[h(s)];if (cp != NULL ) {

if (strcmp (s, cp-> nombre ) == 0) /* primero de la lista */hashtabla[h(s)] = cp->next;

else for (q=cp, cp = cp ->next; cp != NULL; q = cp, cp = cp ->next ){if (strcmp (s, cp->nombre ) == 0) {

q ->next = cp ->next;break;

}}if (cp != NULL ) {

free((char *)cp -> nombre);free ( (pcelda) cp );return (0);

}}return (1); // balde vacio o no lo encontro en la lista

}

9: Hash 30

Tablas de hash abierto usando listas: complejidad

❒ Usando un experimento de tipo Bernouilli se desea introducir i claves, en uno de B baldes, en un conjunto de n experimentos.

❒ Es decir: Se tienen i éxitos en n experimentos Bernouilli con probabilidad 1/B. Considerando como una distribución binomial:

❒ Para 100 baldes se ilustran los largospromedios de las listas con 90 y 50 elementos en la tabla. Se aprecia que es baja la probabilidad de encontrar listas de largo mayor que 4.

9: Hash 31

Tablas de hash abierto: complejidad

❒ En un caso ideal, la función de hash da una distribución uniforme. ❒ Si se tienen n elementos en una tabla de hash abierto con B baldes,

se define el factor de carga como: FC = n/B ❒ Usando listas estas resultan de largo promedio n/B. ❒ Las operaciones demandan en promedio: O( 1 + n/B) ❒ Considerando de costo 1 la evaluación de la función de hash y n/B el

recorrer la lista. ❒ Si B es proporcional a n, las operaciones resultan de costo constante. ❒ Llenar una tabla de hash abierto usando listas con n items tiene

complejidad promedio: O( n(1+n/B) ). Ya que se debe buscar n veces.❒ La complejidad en el peor caso es O(n) si la función de hash inserta

todos los elementos en el mismo balde.❒ Este peor caso se puede reducir a O(log n) usando un árbol auto

balanceado (e.g. arbol AVL) pero esto requiere extra programación y memoria. Vale la pena si se debe evitar retardos a toda costa y si se esperan posibles distribuciones de claves no uniformes.

9: Hash 32

Estrategia de hash cerrado

❒ En otra estrategia llamada de hash cerrado (closed hashing) cuando un nuevo elemento debe ser insertado, se analizan los baldes comenzando con el índice obtenido del hash y se procede en una secuencia de búsqueda hasta que se encuentra un balde desocupado.

❒ Cuando se busca un elemento, los baldes se buscan en el mismo orden que cuando se inserto. Esto hasta que se encuentra el elemento con la clave deseada o hasta que se encuentra un balde no ocupado que indica que la clave no esta en la tabla.

❒ Secuencias de búsqueda típicas (probe secuences) incluyen:❍ Lineal: en la cual el intervalo de búsqueda es fijo (típicamente 1)

❍ Cuadrático: en el cual el intervalo de búsqueda se incrementa agregando valores sucesivos de un polinomial cuadrático al valor inicial dado por la función de hash original

❍ Hashing doble: en el cual el intervalo de búsqueda es calculado por otra función de hash [http://en.wikipedia.org/wiki/Hash_table].

9: Hash 33

índicesclaves elementos

Estrategia de hash cerrado II

❒ Todos los elementos se almacenan en el arreglo mismo o vía punteros en el arreglo mismo, no usando estructuras adicionales.

❒ Un problema es que el numero de elementos (n) no puede exceder el número de baldes (B) en el arreglo. Incluso con una buena función de hash el hash cerrado tiene una seria degradación con factores de carga sobre 0.7. Para muchas aplicaciones esto requiere que se incremente el tamaño del arreglo dinámicamente (dynamic resizing) con los costos asociados [Sedgewick 14.3-14.5].

Fuente: http://en.wikipedia.org/wiki/Hash_table

9: Hash 34

Estrategia de hash cerrado III

❒ En el tiempo al eliminar y agregar elementos debería ocurrir un fenómeno en la cual muchos elementos posiblemente no se encuentran en la posición dada por el índice correspondiente de la función de hash si no que están corridos por el uso que se le ha dado a la tabla en el tiempo. De alguna manera esto podría ser corregido en un procedimiento de reordenamiento periódico de mantención análogo a la défragmentación periódica de los discos duro.

❒ Si se usa hash cerrado sin punteros esto reduce usos de memoria en casos en que los elementos son pequeños y si el factor de carga no es muy bajo. Si el factor de carga es muy bajo entonces hashcerrado sin punteros es poco eficiente porque se reserva mucho espacio que nunca se va a usar.