Método de búsqueda POR FUNCIONES DE HASH Búsqueda mediante transformación de claves (hashing) Es un método de búsqueda que aumenta la velocidad de búsqueda, pero que no requiere que los elementos estén ordenados. Consiste en asignar a cada elemento un índice mediante una transformación del elemento. Esta correspondencia se realiza mediante una función de conversión, llamada función hash. La correspondencia más sencilla es la identidad, esto es, al número 0 se le asigna el índice 0, al elemento 1 el índice 1, y así sucesivamente. Pero si los números a almacenar son demasiado grandes esta función es i nservible. Por ejemplo, se quiere guardar en un array la información de los 1000 usuarios de una empresa, y se elige el núemro de DNI como elemento identificativo. Es inviable hacer un array de 100 .000.000 elementos, sobre todo porque se desaprovecha demasiado espacio. Por eso, se realiza una transformación al número de DNI para que nos de un número menor, por ejemplo coger las 3 últimas cifras para guardar a los empleados en un array de 1000 elementos. Para buscar a uno de ellos, bastaría con realizar la transformación a su DNI y ver si está o no en el ar ray. La función de hash ideal debería ser biyectiva, esto es, que a cada elemento le corresponda un índice, y que a cada índice le corresponda un elemento, pero no siempre es fácil encontrar esa función, e incluso a veces es inútil, ya que puedes no saber el número de elementos a almacenar. La función de hash depende de cada problema y de cada finalidad, y se pueden utilizar con números o cadenas, pero las más utilizadas son:
Restas sucesivas: esta función se emplea con claves numéricas entre las que existen huecos de tamaño conocido, obteniéndose direcciones consecutivas. Por ejemplo, si el número de expediente de un alumno universitario está formado por el año de entrada en la universidad, seguido de un número identificativo de tres cifras, y suponiendo que entran un máximo de 400 alumnos al año, se le asignarían las claves:1998-000 --> 0 = 1998000-1998000 1998-001 --> 1 = 1998001-1998000 1998-002 --> 2 = 1998002-1998000 ... 1998-399 --> 399 = 1998399-1998000 1999-000 --> 400 = 1999000-1998000+400 ... yyyy-nnn --> N = yyyynnn-1998000+(400*(yyy yyyynnn-1998000+(400*(yyyy-1998)) y-1998)) Aritmética modular: el índice de un número es resto de la división de ese número entre un número N prefijado, preferentemente primo. Los números se guardarán en las direcciones de memoria de 0 a N-1. Este método tiene el problema de que cuando hay N+1 elementos, al menos
un índice es señalado por dos elementos (teorema del palomar). A este fenómeno se le llama colisión, y es tratado más adelante. Si el número N es el 13, los números siguientes quedan transformados en:13000000 --> 0 12345678 --> 7 13602499 --> 1 71140205 --> 6 73062138 --> 6 Mitad del cuadrado: consiste en elevar al cuadrado la clave y coger las cifras centrales. Este método también presenta problemas de colisión:123*123=15129 --> 51 136*136=18496 --> 84 730*730=532900 --> 29 301*301=90601 --> 06 625*625=390625 --> 06 Truncamiento: consiste en ignorar parte del número y utilizar los elementos restantes como índice. También se produce colisión. Por ejemplo, si un número de 8 cifras se debe ordenar en un array de 1000 elementos, se pueden coger la primer, la tercer y la última cifras para formar un nuevo número:13000000 --> 100 12345678 --> 138 13602499 --> 169 71140205 --> 715 73162135 --> 715 Plegamiento: consiste en dividir el número en diferentes partes, y operar con ellas (normalmente con suma o multiplicación). También se produce colisión. Por ejemplo, si dividimos los número de 8 cifras en 3, 3 y 2 cifras y se suman, dará otro número de tres cifras (y si no, se cogen las tres últimas cifras):13000000 --> 130=130+000+00 12345678 --> 657=123+456+78 71140205 --> 118 --> 1118=711+402+05 13602499 --> 259=136+024+99 25000009 --> 259=250+000+09
Tratamiento de colisiones:
Pero ahora se nos presenta el problema de qué hacer con las colisiones, qué pasa cuando a dos elementos diferentes les corresponde el mismo índice. Pues bien, hay tres t res posibles soluciones: Cuando el índice correspondiente a un elemento ya está ocupada, se le asigna el primer índice libre a partir de esa posición. Este método es poco eficaz, porque al nuevo elemento se le asigna un índice que podrá estar ocupado por un elemento posterior a él, y la búsqueda se ralentiza, ya que no se sabe la posición exacta del elemento. También se pueden reservar unos cuantos lugares al final del array para alojar a las colisiones. Este método también tiene un problema: ¿Cuánto espacio se debe reservar? Además, sigue la lentitud de búsqueda si el elemento a buscar es una colisión. Lo más efectivo es, en vez de crear un array de número, crear un array de punteros, donde cada puntero señala el principio de una lista enlazada. Así, cada elemento que llega a un determinado índice se pone en el último lugar de la lista de ese índice. El tiempo de búsqueda se reduce considerablemente, considerablemente, y no hace falta poner restricciones al tamaño del array, ya que se pueden añadir nodos dinámicamente a la lista... "LAS TABLAS HASH" En una "tabla hash", los elementos están formados por una pareja: una clave y un valor, como en un SortedList, pero la diferencia está en la forma en que se manejan m anejan internamente estos datos: la "tabla hash" usa una "función de dispersión" para colocar los elementos, de forma que no se pueden recorrer secuencialmente, secuencialmente, pero a cambio el acceso a partir de la clave es muy rápido, más que si hacemos una búsqueda secuencial (como en un array) o binaria (como en un ArrayList ordenado). Un ejemplo de diccionario, parecido al anterior (que es más r ápido de consultar para un dato concreto, pero que no se puede recorrer en orden), podría ser: /*---------------------------*/ /* Ejemplo en C# */ /* HashTable1.cs */ /* */ /* Ejemplo de HashTable: */ /* Diccionario de inform. */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; System.Collections; public class ejemploHashTable { public static void Main() { Creamos e insertamos datos Hashtable miDiccio = new Hashtable(); miDiccio.Add("byte", miDiccio.Add("byte", "8 bits"); miDiccio.Add("pc", miDiccio.Add("pc", "personal computer"); miDiccio.Add("kilobyte", miDiccio.Add("kilobyte", "1024 bytes");
Mostramos algún dato Console.WriteLine( Console.WriteLine( "Cantidad de palabras en el diccionario: {0}", miDiccio.Count miDiccio.Count ); try { Console.WriteLine( Console.WriteLine( "El significado de PC es: {0}", miDiccio["pc"]); } catch (Exception e) { Console.WriteLine( Console.WriteLine( "No existe esa palabra!"); } } } que escribiría en pantalla: Cantidad de palabras en el diccionario: 3 El significado de PC es: personal computer Si un elemento que se busca no existe, se lanzaría una excepción, por lo que deberíamos controlarlo con un bloque try..catch. Lo mismo ocurre si intentamos introducir un dato que ya existe. Una alternativa a usar try..catch es comprobar si el dato ya existe, con el método "Contains" (o su sinónimo "ContainsKey"), como en este ejemplo: /*---------------------------*/ /* Ejemplo en C# */ /* HashTable2.cs */ /* */ /* Ejemplo de HashTable 2: */ /* Diccionario de inform. */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; System.Collections; public class ejemploHashTable2 { public static void Main() { Creamos e insertamos datos Hashtable miDiccio = new Hashtable(); miDiccio.Add("byte", miDiccio.Add("byte", "8 bits"); miDiccio.Add("pc", miDiccio.Add("pc", "personal computer"); miDiccio.Add("kilobyte", miDiccio.Add("kilobyte", "1024 bytes"); Mostramos algún dato Console.WriteLine( Console.WriteLine( "Cantidad de palabras en el diccionario: {0}",
miDiccio.Count miDiccio.Count ); if (miDiccio.Contains("pc")) (miDiccio.Contains("pc")) Console.WriteLine( Console.WriteLine( "El significado de PC es: {0}", miDiccio["pc"]); else Console.WriteLine( Console.WriteLine( "No existe la palabra PC"); } } Otras posibilidades son: borrar un elemento ("Remove"), vaciar toda la tabla ("Clear"), o ver si contiene un cierto valor ("ContainsValue", mucho más lento que buscar entre las claves con "Contains"). Una tabla hash tiene una cierta capacidad inicial, que se amplía automáticamente cuando es necesario. Como la tabla hash es mucho m ucho más rápida cuando está bastante vacía que cuando está casi llena, podemos usar un constructor alternativo, en el que se le indica la capacidad inicial que queremos, si tenemos una idea aproximada de cuántos datos vamos a guardar: Hashtable miDiccio = new Hashtable(500); Una tabla Hash es una tabla donde tienes clave, y valor. El código hash es único para cada valor y cada valor sólo tiene un código hash. Ejemplo de una taba hash: clave 1 : valor"Nombre" clave 2 : valor"Apellido" entonces si tu quieres buscar el nombre, consultas en la tabla por su clave. Ejemplo en java Hashtable ht = new Hashtable(); ht.put("clave1", "Nombre"); ht.put("clave2", "Apellido"); System.out.println(ht.get("clave1"))…
import java.util.*; public class hash { public static void main (String args[]) throws Exception {
Hashtable hash = new Hashtable(10,10); for (int i = 0; i <= 100; i++)
{ Integer entero = new Integer ( i ); hash.put( entero, "Numero : " + i); } for (Enumeration e = hash.keys(); e.hasMoreElements();) e.hasMoreElements();) { System.out.println (hash.get(e.nextElement())); (hash.get(e.nextElement())); } } }
Una tabla hash no es mas que una estructura de datos para asociar valores con llaves. Imaginate algo asi LLave => Valor Carlos => Mexico Jose => China Juan => USA Ernesto => Peru Ahi estariamos relacionando un grupo de nombres con su pais de origen.En java la implementacion seria con la clase Hashtable, aunque mas recientemente se ha favorecido el uso de Hashmap que es muy similar pero con pequeñas diferencias.Esta es una implementacion de una hashtable en java, no hace nada en especial... solo relaciona numeros con mas numeros de forma automática.
VENTAJAS E INCONVENIENTES 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.
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 --->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. INSERCIÓN
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 a la clave del elemento. El resultado de la función resumen ha de mapearse al espacio de direcciones del array 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. Si en la posición de la tabla ya había otro elemento, se ha producido una colisión. Este problema se puede solucionar asociando una lista a cada posición de la tabla, aplicando otra función o buscando el siguiente elemento libre. Estas posibilidades han de considerarse a la hora de recuperar los datos.
Las tablas hash se suelen implementar sobre arrays de una dimensión, aunque se pueden hacer implementaciones implementaciones multi-dimensionales basadas en varias claves. Como en el caso de los arrays, las tablas hash proveen tiempo constante de búsqueda promedio O(1),[1] sin importar el número de elementos en la tabla. Sin embargo, en casos particularmente malos el tiempo de búsqueda puede llegar a O(n), es decir, en función del número de elementos.
Comparada con otras estructuras de arrays asociadas, las tablas hash son más útiles cuando se almacenan grandes cantidades de información.
Las tablas hash almacenan la información en posiciones pseudo-aleatorias, así que el acceso ordenado a su contenido es bastante lento. Otras estructuras como árboles binarios autobalanceables son más rápidos en promedio (tiempo de búsqueda O(log n)) pero la información está ordenada en todo momento.
TABLAS HASH Una tabla hash o mapa hash 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 la tabla hash utiliza para localizar el valor deseado.
QUE ES UNA FUNCIÓN DE HASH
Es una función para resumir o identificar probabilísticamente probabilísticamente un gran conjunto de información, dando como resultado un conjunto imagen finito generalmente menor (un subconjunto de los números naturales por ejemplo). Varían en los conjuntos de partida y de llegada y en cómo afectan a la salida similitudes o patrones de la entrada. Una propiedad fundamental del hashing es que si dos resultados de una misma función son diferentes, entonces las dos entradas que generaron dichos resultados también lo son. Es posible que existan claves resultantes iguales para objetos diferentes, ya que el rango de posibles claves es mucho menor que el de posibles objetos a resumir (las claves suelen tener en torno al centenar de bits, pero los ficheros no tienen un tamaño tam año límite). Son usadas en múltiples aplicaciones, como los arrays asociativos, criptografía, procesamiento de datos y firmas digitales, entre otros. Una buena función de hash es una que experimenta pocas colisiones en el conjunto esperado de entrada; es decir que se podrán identificar unívocamente las entradas (ver función inyectiva). Muchos sistemas relacionados con la seguridad informática usan funciones o tablas hash.