Una tabla Hash es un contenedor asociativo (tipo Diccionario) que permite un
almacenamiento y posterior recuperacin eficientes de elementos (denominados valores) a partir de otros objetos, llamados claves. Tras esta explicacin preliminar vamos a entrar en detalle. Una tabla hash se puede ver como un conjunto de entradas. Cada una de estas entradas tiene asociada una clave nica, y por lo tanto, diferentes entradas de una misma tabla tendrn diferentes claves. Esto implica, que una clave identifica univocamente a una entrada en una tabla hash. Por otro lado, las entradas de las tablas hash estn compuestas por dos componentes, la propia clave y la informacin que se almacena en dicha entrada.
La estructura de las tablas hash es lo que les confiere su gran potencial, ya que hace de ellas unas estructuras extremadamente eficientes a la hora de recuperar informacin almacenada. El tiempo medio de recuperacin de informacin es constante, es decir, no depende del tamao de la tabla ni del nmero de elementos almacenados en la misma. Una tabla hash est formada por un array de entradas, que ser la estructura que almacene la informacin, y por una funcin de dispersin. La funcin de dispersin permite asociar el elemento almacenado en una entrada con la clave de dicha entrada. Por lo tanto, es un algoritmo crtico para el buen funcionamiento de la estructura. Cuando se trabaja con tablas hash es frecuente que se produzcan colisiones. Las colisiones se producen cuando para dos elementos de informacin distintos, la funcin de dispersin les asigna la misma clave. Como se puede suponer, esta solucin se debe arreglar de alguna forma. Para ello las tablas hash cuentan con una funcin de resolucin de colisiones. Existen dos tipos de tablas hash, en funcin de cmo resuelven las colisiones: Encadenamiento separado: Las coliciones se resuelven insertndolas en una lista. De esa forma tendramos como estructura un vector de listas. Al nmero medio de claves por lista se le llama factor de carga y habra que intentar que est prximo a 1. Direccionamiento abierto: Utilizamos un vector como representacin y cuando se produzca una colisin la resolvemos reasignndole otro valor hash a la clave hasta que encontremos un hueco. Qu son las tablas hash Una tabla Hash es un contenedor asociativo (tipo Diccionario) que permite un almacenamiento y posterior recuperacin eficientes de elementos (denominados valores) a partir de otros objetos, llamados claves. Tras esta explicacin preliminar vamos a entrar en detalle. Una tabla hash se puede ver como un conjunto de entradas. Cada una de estas entradas tiene asociada una clave nica, y por lo tanto, diferentes entradas de una misma tabla tendrn diferentes claves. Esto implica, que una clave identifica univocamente a una entrada en una tabla hash. Por otro lado, las entradas de las tablas hash estn compuestas por dos componentes, la propia clave y la informacin que se almacena en dicha entrada.
La estructura de las tablas hash es lo que les confiere su gran potencial, ya que hace de ellas unas estructuras extremadamente eficientes a la hora de recuperar informacin almacenada. El tiempo medio de recuperacin de informacin es constante, es decir, no depende del tamao de la tabla ni del nmero de elementos almacenados en la misma. Una tabla hash est formada por un array de entradas, que ser la estructura que almacene la informacin, y por una funcin de dispersin. La funcin de dispersin permite asociar el elemento almacenado en una entrada con la clave de dicha entrada. Por lo tanto, es un algoritmo crtico para el buen funcionamiento de la estructura. Cuando se trabaja con tablas hash es frecuente que se produzcan colisiones. Las colisiones se producen cuando para dos elementos de informacin distintos, la funcin de dispersin les asigna la misma clave. Como se puede suponer, esta solucin se debe arreglar de alguna forma. Para ello las tablas hash cuentan con una funcin de resolucin de colisiones. Existen dos tipos de tablas hash, en funcin de cmo resuelven las colisiones: Encadenamiento separado: Las colisiones se resuelven insertndolas en una lista. De esa forma tendramos como estructura un vector de listas. Al nmero medio de claves por lista se le llama factor de carga y habra que intentar que est prximo a 1. Direccionamiento abierto: Utilizamos un vector como representacin y cuando se produzca una colisin la resolvemos reasignndole otro valor hash a la clave hasta que encontremos un hueco. Creando y usando tablas Hash en C/C++ Las tablas hash son una implementacin eficiente de una amplia estructura de datos con claves, una estructura a veces conocido como una matriz asociativa o un mapa. Si ests trabajando en C++, tu puedes tomar ventaja del STL Map Container para matrices con claves implementadas mediante rboles binarios, pero este artculo te dar un poco de la teora detrs de cmo funciona una tabla hash. Matrices con claves vs. Matrices indexadas.
Uno de los mayores inconvenientes de un lenguaje como C es que no hay matrices con clave. En una matriz normal de C (tambin denominada matriz indexada), la nica manera de acceder a un elemento sera a travs de su nmero de ndice. Para encontrar el elemento 50 de una matriz denominada "empleados" tienes que acceder a ella de esta manera: empleados [50]; En una matriz con clave, sin embargo, t seras capaz de asociar cada elemento con una "clave", que puede ser cualquier cosa, desde un nombre a un nmero de modelo del producto. Por lo tanto, si tienes una matriz con clave de los registros de los empleados, se puede acceder al registro del empleado "John Brown" de esta manera: empleados ["Brown, John"]; Una forma bsica de matriz con claves se llama tabla hash. En una tabla hash, una clave se utiliza para encontrar un elemento en lugar de un nmero de ndice. Dado que la tabla hash tiene que ser codificado utilizando una matriz indexada, tiene que haber alguna manera de transformar una clave en un nmero de ndice. De esta manera se llama la funcin hash.
Las funciones hash
Una funcin hash puede ser casi cualquier cosa. La manera en que la funcin de hash es en realidad programada depende de la situacin, pero en general la funcin hash debe devolver un valor basado en una clave y el tamao de la matriz en la que est construida la tabla. Adems, una cosa importante que a veces se pasa por alto es que una funcin hash tiene que devolver el mismo valor cada vez que se da la misma clave.
Digamos que queremos organizar una lista de cerca de 200 direcciones por los apellidos de las personas. Una tabla hash sera ideal para este tipo de cosas, pudiendo acceder a los registros con los apellidos como clave.
En primer lugar, tenemos que determinar el tamao de la matriz que estamos usando. Vamos a usar una matriz de 260 elementos, as que puede haber un promedio de 10 espacios de elementos por letra del alfabeto.
Ahora, tenemos que hacer una funcin hash. En primer lugar, vamos a crear una relacin entre letras y nmeros: A -> 0 B -> 1 C -> 2 D -> 3 ...y as sucesivamente hasta Z -> 25. La forma ms sencilla de organizar la tabla hash se basa en la primera letra del apellido.
Ya que tenemos 260 elementos, podemos multiplicar la primera letra del apellido por 10. Por lo tanto, cuando una clave como "Smith" es recibida, la clave se transforma en el ndice de 180 (S es la letra 19 del alfabeto, por lo que S -> 18, y 18 * 10 = 180).
Dado que se utiliza una funcin simple para generar un nmero de ndice rpidamente, y se utiliza el hecho de que el nmero de ndice se puede utilizar para acceder a un elemento directamente, el tiempo de acceso a la tabla de hash es bastante pequeo. Una lista enlazada de claves y elementos no sera tan rpido, ya que tendra que buscar a travs de todos y cada uno de los elementos clave par.
Las colisiones y manejo de colisiones.
Los problemas, por supuesto, surgen cuando tenemos apellidos con la misma letra. As "Webster" y el "Whitney" se correspondera con el mismo nmero de ndice, 22. Una situacin como esta cuando dos claves son enviadas a la misma ubicacin en la matriz se llama una colisin. Si ests tratando de insertar un elemento, puedes encontrarte con que el espacio ya est ocupado por otro.
Por supuesto, podrsa tratar de hacer slo una enorme variedad y por lo tanto hacer que sea casi imposible que una colisin suceda, pero que contradice el objetivo de utilizar una tabla hash. Una de las ventajas de la tabla hash es que es a la vez rpido y pequeo.
Manejo de colisiones con direccionamiento abierto
El algoritmo manipulacin de colisiones ms simple se conoce como el mtodo de direccin abierta o el mtodo de dispersin cerrada. Cuando se agrega un elemento, por ejemplo "Whitney", y te encuentras con que otro elemento ya est ah ("Webster", por ejemplo), entonces pasas al siguiente elemento del espacio (el que est despus de "Webster"). Si esto se llena, te vas a la siguiente, y as sucesivamente, hasta que encuentre un espacio vaco para insertar el nuevo elemento (todos los elementos adicionales vinieron muy bien despus de todo!) ... 220 "Blanco" | <- # # # # # # COLISSION: seguir adelante a la siguiente. 221 "Webster" | <- # # # # # # COLISSION: Siguiente. 222 | Ahhh, perfecto. Inserte aqu. 223 | ... Ya que se modificamos el algoritmo de insercin, tambin tenemos que cambiar la funcin en la que se encuentra el elemento. Tienes que tener alguna manera de verificar que has encontrado el elemento que deseas, y no algn otro elemento. La forma ms sencilla es comparar simplemente las claves (Tiene este registro el apellido "Whitney"? Es ste?). Si el elemento que encontramos no es uno de ellos, simplemente pasa al siguiente elemento hasta llegar al que quieres o te encuentras con un espacio vaco (que significa que el elemento no est en la tabla).
Suena simple, verdad? Bueno, la cosa se complica. Qu pasa si tienes tantas colisiones que te desbordas fuera de la matriz?
Si estas tratando de insertar "Zorba" y todos los elementos estn llenos debido a la manipulacin de la colisin, entonces qu? Mira el ejemplo: ...258 "Whitney" | <- No, no es vaco 259 "Zenn" | No, no est vaco ---------------- <- Ummm, y ahora qu? Lo ms fcil que se puede hacer es ajustar de nuevo el principio. Si todava no hay espacios vacos, entonces tenemos que cambiar el tamao de la matriz, ya que no hay suficiente espacio en la tabla hash para todos los elementos. Si cambiamps el tamao de la matriz, por supuesto, vamos a tener que inventarnos una vuelta de tuerca para nuestra funcin hash (o al menos cmo lo manejamos), de modo que cubra el rango correcto de los valores de nuevo, pero al menos tendremos espacio. (Ten en cuenta que cambiar el tamao de la matriz significa que de vez en cuando se inserta un valor en la lista provocar una operacin de copia suceda (O (n)) pero que en promedio esto ocurre slo una vez para cada n elementos insertados, por lo que la insercin debera estar en un promedio constante de tiempo, O (1). Si no ests seguro de lo que trminos como "O (n") y "Constante de tiempo" quiere decir, echa un vistazo a la serie de artculos Cprogramming.com en la eficiencia de algoritmos (estan en ingls). Como puedes ver, el cambio de tamao no es tan malo - aun as, si sabes la cantidad de espacio que se necesita para empezar, puedes ahorrarte un poco de trabajo.
Manejo de las colisiones con encadenamiento separado
Una segunda estrategia de manipulacin de colisiones es la de almacenar una lista enlazada en cada elemento de la estructura de datos hash. De esta manera, cuando se produce una colisin, puedes agregar el elemento en la lista enlazada que se almacena en el ndice hash. Si tienes un solo elemento con un valor hash en particular, entonces tienes un elemento de lista - sin penalizar el rendimiento. Si tienes un montn de elementos devolviendo un hash al mismo valor, vers una desaceleracin, por supuesto, pero no ms que de lo que veras con colisiones de hash.
Una cosa buena del encadenamiento separado es que tener un montn de valores que devuelven un hash cerca uno del otro es menos importante. Con el direccionamiento abierto, si tienes un conjunto de valores que hacen hash para casi el mismo valor, te quedars sin espacio abierto en esa parte de la matriz. Con encadenamiento separado, cada elemento que tiene un valor hash diferente no tendr impacto en los dems elementos.
Cambio dinmico de tamao basado en factor de carga
En general, no quieres que tu tabla hash crezca hasta quedar completamente llena, ya que esto har que las bsquedas necesiten mucho ms tiempo. Si un valor no est en la matriz, con direccionamiento abierto, tienes que seguir buscando hasta que llegues a un lugar vaco o que vuelva al punto de partida - en otras palabras, con una tabla completamente llena, las bsquedas pueden ser O (n), que es horrible. Una implementacin real de la tabla hash har un seguimiento de su factor de carga, la relacin de los elementos en cuanto al tamao de la matriz. Si tienes una matriz de 10 elementos, con 7 elementos, el factor de carga es de 0,7. De hecho, 0,7 por lo general es el momento adecuado para cambiar el tamao de la matriz subyacente.
La eleccin de un buen algoritmo de hash
Cuanto ms colisiones haya, peor ser el rendimiento de tu tabla hash. Con suficientes elementos de la tabla hash, se puede obtener un rendimiento medio que es bastante bueno - tiempo esencial constante O (1). El truco es hacer que la matriz crezca con el tiempo a medida que se empieza a llenar la matriz. Pero si tienes un montn de elementos que devuelven hash al mismo valor, entonces tendrs que empezar a hacer una bsqueda a travs de una lista de elementos en la que todos tengan el mismo valor hash. Esto puede hacer que las bsquedas de hash pasen de ser de tiempo constante a ser, bueno, de tiempo lineal en el nmero de elementos. Imagina que tu funcin hace hash de todos los valores a 0, poniendolos en el primer elemento de la matriz. En ese caso sera slo una forma muy complicada de implementar una bsqueda lineal.
La eleccin de un algoritmo de hash bien puede requerir algn tipo de atencin y la experimentacin, y depender de tu dominio del problema. Si ests trabajando con los nombres, es probable que no quieras un algoritmo de hash que slo se fije en la primera letra, ya que las letras del alfabeto no se utilizan de manera uniforme - encontrars muchos ms nombres comienzan con S o con Z. Tambin querrs que tus funciones hash sean rpidas - no deseas perder todos el tiempo que vienes ahorrando en tu tabla hash porque est calculando la funcin de hash muy lentamente. Es un equilibrio delicado. Aqu hay un buen ejemplo de una funcin hash, de la mano de azillionmonkeys.com. La explicacin est en ingls pero la intencin en entender el cdigo.
Ahora ests listo para implementar tu primera tabla hash! Dale una oportunidad. No es demasiado difcil, y el resultado final es bastante til.