tabla hash - WordPress.com

Transcripción

tabla hash - WordPress.com
INFORMATICA 5260
TABLAS HASH
TABLAS HASH


Una tabla Hash es un contenedor asociativo (tipo Diccionario)
que permite un almacenamiento y posterior recuperación
eficientes de elementos (denominados valores) a partir de
otros objetos, llamados claves.
Una tabla hash, mapa hash o tabla de dispersión es una
estructura de datos que asocia llaves o claves con valores. La
operación principal que soporta de manera eficiente es la
búsqueda: permite el acceso a los elementos (teléfono y
dirección, por ejemplo) almacenados a partir de una clave
generada (usando el nombre o número de cuenta, por
ejemplo). Funciona transformando la clave con una función
hash en un hash, un número que identifica la posición (casilla o
cubeta) donde la tabla hash localiza el valor deseado.


Las tablas hash son estructuras de datos que se utilizan
para almacenar un número elevado de datos sobre los
que se necesitan operaciones de búsqueda e inserción
muy eficientes. Una tabla hash almacena un conjunto de
pares “(clave, valor)”. La clave es única para cada elemento
de la tabla y es el dato que se utiliza para buscar un
determinado valor.
Un diccionario es un ejemplo de estructura que se puede
implementar mediante una tabla hash. Para cada par, la
clave es la palabra a buscar, y el valor contiene su
significado. El uso de esta estructura de datos es tan
común en el desarrollo de aplicaciones que algunos
lenguajes las incluyen como tipos básicos.
Ejemplo de Tabla Hash
Ventajas de las tablas Hash

Una tabla hash tiene como principal ventaja que el acceso
a los datos suele ser muy rápido si se cumplen las
siguientes condiciones:


Una razón de ocupación no muy elevada (a partir del 75% de
ocupación se producen demasiadas colisiones y la tabla se
vuelve ineficiente).
Una función resumen que distribuya uniformemente las claves.
Si la función está mal diseñada, se producirán muchas
colisiones.
Desventajas de las Tablas Hash

Los inconvenientes de las tablas hash son:



Necesidad de ampliar el espacio de la tabla si el volumen de
datos almacenados crece. Se trata de una operación costosa.
Dificultad para recorrer todos los elementos. Se suelen
emplear listas para procesar la totalidad de los elementos.
Desaprovechamiento de la memoria. Si se reserva espacio para
todos los posibles elementos, se consume más memoria de la
necesaria; se suele resolver reservando espacio únicamente
para punteros a los elementos.
Implementación de una Tabla Hash
La implementación de una tabla hash está basada en los
siguientes elementos:



Una tabla de un tamaño razonable para almacenar los pares
(clave, valor).
Una función “hash” que recibe la clave y devuelve un índice
para acceder a una posición de la tabla.
Un procedimiento para tratar los casos en los que la función
anterior devuelve el mismo índice para dos claves distintas.
Esta situación se conoce con el nombre de colisión.
Las posibles implementaciones de cada uno de estos tres
elementos se traducen en una infinidad de formas de
implementar una tabla hash.
Funcionamiento de una Tabla Hash
Las operaciones básicas implementadas en las tablas hash son:


inserción(llave, valor). Para almacenar un elemento en la tabla
hash se ha de convertir su clave a un número. Esto se consigue
aplicando la función resumen (hash) a la clave del elemento. El
resultado de la función resumen ha de mapearse al espacio de
direcciones del vector que se emplea como soporte, lo cual se
consigue con la función módulo. Tras este paso se obtiene un índice
válido para la tabla. El elemento se almacena en la posición de la tabla
obtenido en el paso anterior.
búsqueda(llave) que devuelve valor. Para recuperar los datos, es
necesario únicamente conocer la clave del elemento, a la cual se le
aplica la función resumen. El valor obtenido se mapea al espacio de
direcciones de la tabla. Si el elemento existente en la posición
indicada en el paso anterior tiene la misma clave que la empleada en
la búsqueda, entonces es el deseado. Si la clave es distinta, se ha de
buscar el elemento según la técnica empleada para resolver el
problema de las colisiones al almacenar el elemento
Función Hash



A cada elemento se le asigna una determinada posición
de la tabla por medio de la función hash.
Más concretamente, una función Hash debe transformar
claves(normalmente enteros o cadenas de caracteres) en
enteros en un rango [0..M-1], donde M es el número de
registros que podemos manejar con la memoria de que
dispongamos.
Como factores a tener en cuenta para la elección de la
función h(k) están que minimice las colisiones y que sea
relativamente rápida y fácil de calcular, aunque la situación
ideal sería encontrar una función h que generara valores
aleatorios uniformemente sobre el intervalo [0..M-1].



Un problema bastante común que ocurre con las
funciones hash es el aglomeramiento.
El aglomeramiento ocurre cuando la estructura de la
función hash provoca que llaves usadas comúnmente
tiendan a caer muy cerca unas de otras o incluso
consecutivamente en la tabla hash.
Esto puede degradar el rendimiento de manera
significativa, cuando la tabla se llena usando ciertas
estrategias de resolución de colisiones, como el sondeo
lineal.
Funciones Hash más usadas

Hash de División: En este caso la función se calcula
simplemente como h(k) = k mod M usando el 0 como
el primer índice de la tabla hash de tamaño M.

Hash de Multiplicación : Esta técnica trabaja
multiplicando la clave k por sí misma o por una constante,
usando después alguna porción de los bits del producto
como una localización de la tabla hash.
Colisiones




Cuando se trabaja con tablas hash es frecuente que se
produzcan colisiones.
Las colisiones se producen cuando para dos elementos
de información distintos, la función de dispersión les
asigna la misma clave.
Como se puede suponer, esta solución se debe arreglar
de alguna forma.
Para ello las tablas hash cuentan con una función de
resolución de colisiones.
Tipos de Tablas Hash según la resolución de
las colisiones
Existen dos tipos de tablas hash, en función de cómo
resuelven las colisiones:


Direccionamiento Cerrado, Encadenamiento
separado o Hashing abierto
Direccionamiento abierto o Hashing cerrado
Direccionamiento Cerrado, Encadenamiento
separado o Hashing abierto


Las colisiones se resuelven insertándolas en una lista. De
esa forma tendríamos como estructura un vector de
listas. Al número medio de claves por lista se le llama
factor de carga y habría que intentar que esté próximo a
1.
En la técnica más simple de encadenamiento, cada casilla
en el array referencia una lista de los registros insertados
que colisionan en la misma casilla. La inserción consiste en
encontrar la casilla correcta y agregar al final de la lista
correspondiente. El borrado consiste en buscar y quitar
de la lista.
Ventajas y desventajas del Direccionamiento Cerrado,
Encadenamiento separado o Hashing abierto


La técnica de encadenamiento tiene ventajas sobre
direccionamiento abierto. Primero el borrado es simple y segundo el
crecimiento de la tabla puede ser pospuesto durante mucho más
tiempo dado que el rendimiento disminuye mucho más lentamente
incluso cuando todas las casillas ya están ocupadas. De hecho,
muchas tablas hash encadenadas pueden no requerir crecimiento
nunca, dado que la degradación de rendimiento es lineal en la
medida que se va llenando la tabla. Por ejemplo, una tabla hash
encadenada con dos veces el número de elementos recomendados,
será dos veces más lenta en promedio que la misma tabla a su
capacidad recomendada.
Las tablas hash encadenadas heredan las desventajas de las listas
ligadas. Cuando se almacenan cantidades de información pequeñas,
el gasto extra de las listas ligadas puede ser significativo. También los
viajes a través de las listas tienen un rendimiento de caché muy
pobre.
Visión gráfica (hashing abierto)

Desde un “gran” Universo sólo un número reducido de claves serán
consideradas.
Universo de Claves
Claves usadas
16
Función de
mapeo
o Función de hash
Lista Enlazada
Direccionamiento abierto o Hashing
cerrado



Utilizamos un vector como representación y cuando se
produzca una colisión la resolvemos reasignándole otro
valor hash a la clave hasta que encontremos un hueco.
Las tablas hash de direccionamiento abierto pueden
almacenar los registros directamente en el array.
Las colisiones se resuelven mediante un sondeo del array,
en el que se buscan diferentes localidades del array
(secuencia de sondeo) hasta que el registro es encontrado
o se llega a una casilla vacía, indicando que no existe esa
llave en la tabla.
Direccionamiento abierto o Hashing
cerrado (cont.)
Las secuencias de sondeo más socorridas incluyen:
 sondeo lineal en el que el intervalo entre cada intento es
constante (frecuentemente 1). Ofrece el mejor rendimiento
del caché, pero es más sensible al aglomeramiento
 sondeo cuadrático en el que el intervalo entre los intentos
aumenta linealmente (por lo que los índices son descritos por
una función cuadrática). Se sitúa en medio del sondeo lineal y
del doble hasheo.
 doble hasheo en el que el intervalo entre intentos es
constante para cada registro pero es calculado por otra
función hash. Tiene pobre rendimiento en el caché pero
elimina el problema de aglomeramiento. También puede
requerir más cálculos que las otras formas de sondeo.
Ventajas y desventajas del Direccionamiento
abierto o Hashing cerrado


La gran ventaja de hashing cerrado es que elimina totalmente los
punteros usados en la lista enlazada. Se libera así espacio de
memoria, el que puede ser usado en más entradas de la tabla y
menor número de colisiones.
Una influencia crítica en el rendimiento de una tabla hash de
direccionamiento abierto es el porcentaje de casillas usadas en el
array. Conforme el array se acerca al 100% de su capacidad, el
número de saltos requeridos por el sondeo puede aumentar
considerablemente. Una vez que se llena la tabla, los algoritmos de
sondeo pueden incluso caer en un círculo sin fin. Incluso utilizando
buenas funciones hash, el límite aceptable de capacidad es
normalmente 80%. Con funciones hash pobremente diseñadas el
rendimiento puede degradarse incluso con poca información, al
provocar aglomeramiento significativo. No se sabe a ciencia cierta
qué provoca que las funciones hash generen aglomeramiento, y es
muy fácil escribir una función hash que, sin querer, provoque un nivel
muy elevado de aglomeramiento.
Visión gráfica (hashing cerrado)

Desde un “gran” Universo sólo un número reducido de claves serán
consideradas.
Universo de Claves
Claves usadas
20
Función de
mapeo
Función de hash
La “lista” se almacena
en la misma tabla
Implementación de una tabla Hash en JAVA


Para esto se utiliza la clase hashtable la cual forma parte
del paquete útil.
La clase hashtable tiene como conveniencia que permite
definir los tipos de datos de las claves y valores que
contiene. Por ejemplo:
import java.util.Hashtable;
Hashtable<String, String> capitales = new Hashtable<String, String>();
capitales.put("España","Madrid");
capitales.put("Argentina","Buenos Aires");
Añadir, eliminar y consultar elementos
de una Hashtable

Como hemos visto en el ejemplo del apartado anterior,
para añadir elementos a la Hashtable se utiliza el método
“put”. Otros métodos para el manejo y consulta de
Hashtables son:




remove(clave) – Eliminar un par (clave, valor) identificado
por su clave
get(clave) – Obtener el valor asociado a una clave
containsKey(clave) – Determinar si una clave existe en la
hashtable
contains(valor), containsValue(valor) – Estos dos
métodos son equivalentes, y devuelven true si el argumento
existe en la tabla como un valor asociado a una clave.
A continuación vemos un ejemplo de uso de estos métodos:
// Elimina una entrada de la hashtable
capitales.remove("España");
// Obtener el número de entradas en la hashtable
System.out.println("Tamaño de la tabla de capitales: " + capitales.size());
// Consultar la existencia de una clave en la hashtable
String pais = "Argentina";
if (capitales.containsKey(pais)) {
System.out.println("La capital de " + pais + " es: " + capitales.get(pais));
} else {
System.out.println("La tabla no contiene la capital de " + pais);
}
Recorrer las entradas de una Hashtable

El método keys() devuelve un objeto de la clase Enumeration
que contiene todas las claves de la tabla. Con él, podemos
recorrer las entradas existentes en la misma:
java.util.Enumeration claves = capitales.keys();
while( claves.hasMoreElements() )
{
Object clave = claves.nextElement();
Object valor = capitales.get(clave);
System.out.println("Pais: "+clave.toString()+", capital: " +valor.toString());
}

Del mismo modo, existe el método elements() que devuelve
un objeto de la clase Enumeration que contiene todos los
valores de la tabla. También hay un método values(), que
devuelve un objeto de la clase Collection con todos los
valores de la tabla.
Recorrer ordenadamente las entradas de
una Hashtable


El método keySet() devuelve un objeto de la clase Set
que contiene todas las claves de la Hashtable.
Para recorrer la Hashtable en orden de clave ascencente,
podemos convertir este Set en un Array, y ordenarlo con
el método sort():

String[] claves = (String[]) capitales.keySet().toArray(new String[0]);
java.util.Arrays.sort(claves);
for(String clave : claves) {
System.out.println(clave + " : " + capitales.get(clave));
}
Ejemplo completo de una hashtable en
JAVA
import java.util.*;
public class Direccion {
public static void main(String[] args) {
Hashtable direccion = new Hashtable();
Integer ocho = new Integer(8000);
direccion.put("calle","Primavera");
direccion.put("numero", ocho);
direccion.put("colonia"," La Silla ");
direccion.put("ciudad"," Monterrey ");
direccion.put("estado"," Nuevo Leon ");
direccion.put("pais","Mexico");
String miciudad = (String) direccion.get("ciudad");
String miestado = (String) direccion.get("estado");
String micalle = (String) direccion.get("calle");
Integer minumero = (Integer) direccion.get("numero");
System.out.println("Direccion : " + micalle + " " + minumero);
System.out.println("Lugar: " + miciudad + "," + miestado);
}
}
Explicación del ejemplo
Esta Clase únicamente contiene su método principal (main)
el cual contiene las siguientes funcionalidades:



Se define una Clase Hashtable con la referencia direccion.
Posteriormente se define un número Integer; este es un de los casos
donde deben ser utilizados "Wrappers" para primitivos, puesto
que el elemento será integrado a un contenedor ("Hashtable") en
forma de Objeto es necesario convertir ("Wrap") el primitivo como
Objeto.
A través del método put son colocados diversos juegos de datos en
el "Hashtable", el primer elemento indica el nombre ("key") del
elemento, mientras el segundo indica el Objeto/Valor de este
nombre ("key"); nótese los Objetos/Valores pueden ser de cualquier
tipo, en este ejemplo se colocan diversos Strings y la referencia del
Objeto Integer.
Explicación del ejemplo (2)



Mediante el método get son extraídos algunos valores del
"Hashtable" mediante el correspondiente nombre ("key").
Nótese que dicha extracción requiere que sea realizado un
"Casting" explicito, lo cual es llevado acabo a través de
paréntesis ( ) ; esto se debe a que la extracción es llevada
acabo de una Clase genérica ("Hashtable") hacia una más
especifica ya sea String o Integer ("Down-Casting").
Finalmente son impresos a pantalla algunos valores extraídos
del "Hashtable".

Documentos relacionados