Diseño y estructura de datos y especificación de problemas

48
Capítulo 4 Tablas Se quiere estudiar el TAD de las funciones f : K V que se aplican sobre un dominio K y dan un resultado sobre un codominio V ; a los valores del dominio los llamamos claves (ing., key ; también denominados índices o identificadores) y a los valores del codominio, información o simplemente valores (ing., value); si f(k ) = v, diremos que v es la información asociada o asignada a k. En el caso general, el dominio K tendrá una cardinalidad muy grande y nuestras implementaciones deberán tratar adecuadamente esta característica. Las funciones que consideramos en este capítulo pueden ser totales o parciales; en el segundo caso, llamaremos clave indefinida a toda clave que no esté en el dominio de la función. Dado este modelo matemático queda claro que, así como las estructuras lineales están orientadas al acceso consecutivo a todos sus elementos (ya sea ordenadamente o no), las tablas están orientadas a su acceso individual: interesa definir la función en un punto del dominio, dejar este punto indefinido y aplicar la función sobre él. El TAD de las funciones es conocido en el ámbito de las estructuras de la información con el nombre de tabla (ing., lookup table o symbol table, aunque la notación no es totalmente estándar; algunos textos también lo denominan estructura funcional o diccionario). En este contexto, es frecuente considerar las tablas como conjuntos de pares de clave y de valor, con la restricción de que no haya dos pares con la misma clave, de manera que la clave identifica unívocamente el par. A los pares los llamaremos elementos definidos o, simplemente, elementos de la tabla. La aplicación del tipo en el campo de la programación es diversa; por ejemplo, se emplea para implementar tablas de símbolos de compiladores o de sistemas operativos: la clave es el identificador de los símbolos que se almacenan y la información puede ser muy diversa (direcciones de memoria, dimensión, etc.). En el resto del capítulo se estudia la especificación y la implementación del TAD; de manera especial, se busca una implementación con el objetivo de que las operaciones tengan un coste temporal Θ(1), lo que conseguiremos con las denominadas tablas de dispersión. Tablas 171 __________________________________________________________________________________

description

diseño y estructura de datos complejidad algorítmicaespecificación de tipos abstractos implementar de tiempos abstractossecuencias, tablasHeap, avl trie arboles binarios.Grafos y relaciones binarias.

Transcript of Diseño y estructura de datos y especificación de problemas

  • Captulo 4 Tablas

    Se quiere estudiar el TAD de las funciones f : K V que se aplican sobre un dominio K ydan un resultado sobre un codominio V ; a los valores del dominio los llamamos claves (ing.,key ; tambin denominados ndices o identificadores) y a los valores del codominio,informacin o simplemente valores (ing., value); si f(k ) = v, diremos que v es la informacinasociada o asignada a k. En el caso general, el dominio K tendr una cardinalidad muygrande y nuestras implementaciones debern tratar adecuadamente esta caracterstica. Lasfunciones que consideramos en este captulo pueden ser totales o parciales; en el segundocaso, llamaremos clave indefinida a toda clave que no est en el dominio de la funcin. Dadoeste modelo matemtico queda claro que, as como las estructuras lineales estn orientadasal acceso consecutivo a todos sus elementos (ya sea ordenadamente o no), las tablas estnorientadas a su acceso individual: interesa definir la funcin en un punto del dominio, dejareste punto indefinido y aplicar la funcin sobre l.

    El TAD de las funciones es conocido en el mbito de las estructuras de la informacin con elnombre de tabla (ing., lookup table o symbol table, aunque la notacin no es totalmenteestndar; algunos textos tambin lo denominan estructura funcional o diccionario). En estecontexto, es frecuente considerar las tablas como conjuntos de pares de clave y de valor,con la restriccin de que no haya dos pares con la misma clave, de manera que la claveidentifica unvocamente el par. A los pares los llamaremos elementos definidos o,simplemente, elementos de la tabla.

    La aplicacin del tipo en el campo de la programacin es diversa; por ejemplo, se emplea paraimplementar tablas de smbolos de compiladores o de sistemas operativos: la clave es elidentificador de los smbolos que se almacenan y la informacin puede ser muy diversa(direcciones de memoria, dimensin, etc.).

    En el resto del captulo se estudia la especificacin y la implementacin del TAD; de maneraespecial, se busca una implementacin con el objetivo de que las operaciones tengan uncoste temporal (1), lo que conseguiremos con las denominadas tablas de dispersin.

    Tablas 1 7 1__________________________________________________________________________________

  • 4.1 Especificacin

    Sean K y V dos dominios tales que V presenta un valor indefinido que denotaremos por ,sea f : K V una tabla y sean k K una clave y v V un valor. Las operaciones del TAD son:

    - Crear la tabla vaca: crea, devuelve la funcin definida como dom() = .- Asociar informacin a una clave: asigna(f, k, v), devuelve la funcin g definida como:

    dom(g) = dom(f) {k} g(k) = v. k': k'dom(g) - {k}: g(k') = f(k').

    - Separar la informacin correspondiente a una clave: borra(f, k), devuelve la funcin gdefinida como:

    dom(g) = dom(f) - {k}. k': k'dom(g): g(k') = f(k').

    - Consultar el valor asociado a una clave: consulta(f, k) , devuelve f(k ) si kdom(f ), ydevuelve si kdom(f ).

    Notemos que, en realidad, el modelo aqu presentado se corresponde con las funcionestotales, ya que toda clave tiene un valor asociado. En caso de que el valor indefinido no sepueda identificar en V, se puede forzar que la funcin consulta devuelva un booleanoadicional indicando si la clave estaba o no definida, o bien se puede aadir una operacinms, definida?, que lo averige, de manera que consulta o borra sobre una clave no definidad error, y entonces el modelo de las tablas corresponda realmente a las funciones parciales.

    En la fig. 4.1 se presenta una especificacin parametrizada para el modelo de las funcionestotales con la signatura introducida arriba. Sus parmetros formales son: los gneros para lasclaves y los valores, la igualdad de las claves y el valor indefinido. Por ello, usamos losuniversos de caracterizacin ELEM_= y ELEM_ESP : el primero define las claves y elsegundo los valores; algunos de estos smbolos se renombran, por motivos de legibilidad1.Por lo que al resto del universo se refiere, notemos la semejanza con la especificacin tpicade los conjuntos. Destaquemos que la funcin borra deja la tabla inalterada si la clave noexiste, que la operacin consulta devuelve el valor indefinido si la clave buscada estindefinida y que la operacin asigna, si la clave ya est definida, le asigna un nuevo valor (otraopcin sera dejarla inalterada, o bien que la funcin asigna devolviera un segundo valor talque, si la clave estuviera definida, devolviera el valor asociado a la clave y dejara la tablainalterada, o bien asociara a la clave el nuevo valor). Notemos tambin la ecuacin de errorque evita la insercin del valor indefinido como pareja de una clave.

    En una instancia de las tablas es necesario asociar smbolos a todos estos parmetrosformales; por ejemplo, podemos definir una tabla cuyas claves sean enteros, la informacincadenas de caracteres y el valor indefinido una cadena especial, no vlida como informacinnormal (algo as como "*&%$#!", por ejemplo).

    1 7 2 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    1 Los renombramientos de los parmetros formales slo tienen vigencia en el universo que los efecta.

  • universo TABLA (ELEM_=, ELEM_ESP) esrenombra ELEM_=.elem por clave

    ELEM_ESP.elem por valor, ELEM_ESP.esp por indefusa BOOLtipo tablaops crea: tabla

    asigna: tabla clave valor tablaborra: tabla clave tablaconsulta: tabla clave valor

    error kclave; ttaula: asigna(t, k, indef)ecns k,k1,k2clave; v,v1,v2valor; ttaula

    Ecuaciones impurificadoras:1) asigna(asigna(t, k, v1), k, v2) = asigna(t, k, v2)2) [k1 k2] asigna(asigna(t, k1, v1), k2, v2) =

    asigna(asigna(t, k2, v2), k1, v1)Axiomas para borra:

    1) borra(crea, k) = crea2) borra(asigna(t, k, v), k) = borra(t, k)3) [k1 k2] borra(asigna(t, k1, v1), k2) = asigna(borra(t, k2), k1, v1)

    Axiomas para consulta:1) consulta(crea, k) = indef2) consulta(asigna(t, k, v), k) = v3) [k1 k2] consulta(asigna(t, k1, v1), k2) = consulta(t, k2)

    funiverso

    universo ELEM_ESP caracterizatipo elemops esp: elem

    funiverso

    Fig. 4.1: especificacin del TAD tabla.

    Una variante del TAD tabla bastante usada consiste en considerar el modelo de losconjuntos de elementos de V, P(V ), con operaciones de insercin y supresin individual deelementos y de comprobacin de pertenencia, siendo V un dominio que presenta unafuncin id : V K, de manera que cada elemento del conjunto est identificado por unaclave que sirva para hacerle referencia; a veces, el identificador ser el mismo elemento yentonces V y K sern el mismo gnero e id ser la funcin identidad, y el resultado serequivalente al universo CJT_ de la fig. 1.29 con el aadido de la supresin. La signatura

    Tablas 1 7 3__________________________________________________________________________________

  • propuesta para este modelo aparece en la fig. 4.2 y su especificacin queda como ejerciciopara el lector. Las implementaciones y los algoritmos sobre tablas que aparecen en el restodel captulo se pueden adaptar a este TAD con muy poco esfuerzo.

    universo CONJUNTO (ELEM_CJT) es universo ELEM_CJT caracterizausa BOOL usa ELEM_ESPtipo conjunto tipo claveops ops

    crea: conjunto id: elem claveaade: conjunto elem conjunto _=_, __: clave clave boolborra: conjunto clave conjunto ecns ... las de _=_ y __consulta: conjunto clave elem funiversoest?: conjunto clave bool

    funiverso

    Fig. 4.2: una signatura para el TAD de los conjuntos.

    4.2 Implementacin

    Estudiamos en esta seccin las diferentes posibilidades para implementar el TAD de lastablas. Comenzamos usando diversas variantes de listas hasta llegar a las tablas dedispersin, que son de la mxima eficiencia para las operaciones del tipo. A lo largo de laseccin usaremos dos medidas de eficiencia que nos permitan comparar las organizaciones:

    - Sn(k ): nmero necesario de comparaciones entre claves en una tabla de n elementospara encontrar el elemento de clave k (ing., successful search).

    - Un(k ): nmero necesario de comparaciones entre claves en una tabla de n elementosen una bsqueda sin xito del elemento de clave k (ing., unsuccessful search).

    Notemos que Sn(k ) es un factor importante en el clculo del coste de la modificacin y lasupresin de elementos ya existentes y en la consulta de claves definidas, mientras queUn(k ) determina el coste de la insercin de nuevos elementos dentro de la tabla, de lasupresin de elementos dando como referencia una clave indefinida y de la consulta declaves indefinidas. Generalmente, a partir de los valores de Sn(k ) y Un(k ) se puede determinarel coste de las operaciones del TAD; ahora bien, a veces puede haber otros factores queinfluyan en la eficiencia (por ejemplo, seguimiento de encadenamientos, movimiento deelementos, etc.), en cuyo caso se destacar explcitamente.

    A veces, estas magnitudes sern variables aleatorias, en cuyo caso [Sn(k)] y [Un(k)] sernsus esperanzas2.

    1 7 4 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    2 Pese a que sera interesante, en este libro no se estudian otras medidas estadsticas ms que a nivelmuy superficial y en algn caso concreto; para profundizar en el tema, consultar [GoB91] y [Knu73].

  • 4.2.1 Implementacin por listas desordenadas

    Consiste en organizar una lista no ordenada cuyos elementos sean las claves definidas consu valor asociado. Claramente se cumple que [Sn(k )] = (n+1)/2 y Un(k ) = n. As, el costeasinttico de las operaciones del TAD tabla es (n), frente a otros esquemas logartmicos oconstantes que se introducen a continuacin. No obstante, su simplicidad hace posible sueleccin, para n pequea, o si no hay restricciones de eficiencia.

    Es interesante conocer un par de estrategias tiles en determinadas circunstancias (aunqueno reducen el coste asinttico):

    - Si se dispone a priori de las probabilidades de consulta de cada clave y, a partir de unmomento dado, las actualizaciones son relativamente irrelevantes respecto a lasconsultas, se pueden mantener ordenadas las claves segn estas probabilidadesdentro de la lista, y se obtiene como resultado [Sn(k )] = x : 1 x n : x .px, donde pxrepresenta la probabilidad de consulta de la x-sima clave ms probable.

    - En las bsquedas con xito puede reorganizarse la lista para que los elementos que seconsulten queden cerca del inicio, con la suposicin de que las claves no sonequiprobablemente consultadas y que las ms consultadas hasta al momento son lasque se consultarn ms en el futuro; es decir, se tiende precisamente a una listaordenada por probabilidades. Esta estrategia se conoce con el nombre de bsquedasecuencial autoorganizativa (ing., self-organizing sequential search) y hay variasimplementaciones posibles entre las que destacamos la denominada bsqueda conmovimiento al inicio (ing., move to front ), en la que el elemento consultado se lleva alinicio de la lista, y la denominada bsqueda por transposicin (ing., transpose), dondese intercambia el elemento consultado con su predecesor. En general, el segundomtodo da mejores resultados que el primero cuando las probabilidades de consultade las diversas claves no varan en el tiempo (los saltos de los elementos dentro la listason ms cortos, con lo que su distribucin es ms estable); por otro lado, si ladistribucin es muy sesgada el primer mtodo organiza los elementos msrpidamente. Notemos tambin que, en el caso de representar las listassecuencialmente, los desplazamientos exigen copias de elementos con los problemasmencionados en el apartado 3.3.2.a).

    4.2.2 Implementacin por listas ordenadas

    Aplicable slo si las claves presentan una operacin < de comparacin que defina un ordentotal, es una organizacin til cuando las consultas predominan sobre las actualizaciones,porque aunque estas ltimas queden (n), las primeras reducen su coste. Tambin puedenusarse si la tabla tiene dos estados bien diferenciados en el tiempo, el primero en el quepredominen las actualizaciones y el segundo donde predominen las consultas; entonces se

    Tablas 1 7 5__________________________________________________________________________________

  • puede mantener la lista desordenada durante el primer estado y aplicar un algoritmo deordenacin de coste (nlogn) antes de comenzar el segundo. En todo caso, aparte delcoste temporal, es necesario recordar que, si la implementacin elegida para las listas essecuencial, las actualizaciones exigen movimientos de elementos. A continuacin, citamoslos tres esquemas de bsqueda habituales.

    a) Lineal (secuencial)Ya presentado en la seccin 3.3, consiste en recorrer la lista desde el principio hastaencontrar el elemento buscado o llegar al final. La bsqueda sin xito acaba antes que en elcaso desordenado, [Un(k )] = [Sn(k )] = (n+1)/2, aunque el coste asinttico sigue siendolineal.3

    b) DicotmicaTambin conocida como bsqueda binaria (ing., dichotomic o binary search), es aplicableslo cuando la lista se representa secuencialmente dentro de un vector y consiste endescartar sucesivamente la mitad de las posiciones del vector hasta encontrar el elementobuscado, o bien acabar. Para ello, a cada paso se compara el elemento buscado con elelemento contenido en la posicin central del trozo de vector en examen; si es mspequeo, se descartan todos los elementos de la derecha, si es ms grande, los de laizquierda, y si es igual, ya se ha encontrado.

    En la fig. 4.3 se da una versin iterativa del algoritmo de bsqueda dicotmica. Suponemosque las tablas se representan por un vector A dimensionado de uno a un mximopredeterminado y un apuntador sl de sitio libre; suponemos tambin que cada posicin delvector es una tupla de clave y valor. Los apuntadores izq y der delimitan el trozo del vectoren examen, tal como establece el invariante; debemos destacar, sin embargo, que para queel invariante sea correcto es necesario imaginar que las posiciones 0 y sl de la tabla estnocupadas por dos elementos fantasma tales que sus claves son, respectivamente, la clavems pequea posible y la clave ms grande posible dentro del dominio de las claves. Elanlisis del algoritmo revela que Sn(k ) y Un(k ) valen aproximadamente log2n; el coste de laconsulta es, pues, (logn).

    1 7 6 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    3 Hay un algoritmo que asegura un coste O(n) en el caso medio, pero que queda (n) en el caso peor,que se basa en la insercin de nuevos encadenamientos, que permiten efectuar saltos ms largos. En[BrB87, pp. 248-250] se presenta una versin probabilistica de este algoritmo, que consigue reducir elcoste del caso peor al caso medio.

  • funcin consulta (t es tabla; k es clave) devuelve valor esvar izq, der, med son nat; encontrado es bool; v es valor fvar

    izq := 0; der := t.sl; encontrado := falsomientras (der - izq > 1) encontrado hacer

    { I 0 izq < der t.sl t.A[izq].k < k < t.A[der].k encontrado t.A[med].k = k }

    med := (izq + der) / 2opcin

    caso t.A[med].k = k hacer encontrado := ciertocaso k < t.A[med].k hacer der := medcaso t.A[med].k < k hacer izq := med

    fopcinfmientrassi encontrado entonces v := t.A[med] si no v := indefinido fsi

    devuelve v

    Fig. 4.3: algoritmo de bsqueda dicotmica.

    c) Por interpolacinTambin para listas representadas secuencialmente, la bsqueda por interpolacin (ing.,interpolation search ; tambin conocida como estimated entry search) sigue la mismaestrategia que la anterior con la diferencia de que, en vez de consultar la posicin central, seconsulta una posicin que se interpola a partir del valor de la clave que se quiere buscar y delos valores de la primera y la ltima clave del trozo del vector en examen. Si suponemos quelas claves se distribuyen uniformemente entre sus valores mnimo y mximo, el resultado esque tanto [Sn(k )] como [Un(k )] valen (loglogn), con la funcin de interpolacin F :

    F(A, k, i, j) = (k-A[i]) / (A[j]-A[i]) . ( j - i ) + i

    Si la distribucin no es uniforme, aunque tericamente se podra corregir la desviacin paramantener este coste bajo (conociendo a priori la distribucin real de las claves), en la prcticasera complicado; por ejemplo, si p : 1 p n-1: A[p] = p y A[n] = , en el caso de buscar elelemento n-1, el coste resultante es lineal. Para evitar este riesgo, en [GoB91, pp. 42-43] sepropone combinar un primer paso de acceso a la tabla usando el algoritmo de interpolacin y,a continuacin, buscar secuencialmente en la direccin adecuada.

    Notemos que la bsqueda por interpolacin dentro de una lista slo funciona cuando lasclaves son numricas (o tienen una interpretacin numrica), y no estn repetidas (como esel caso que aqu nos ocupa, en el que la lista representa una tabla).

    Tablas 1 7 7__________________________________________________________________________________

  • 4.2.3 Implementacin por vectores de acceso directo

    Una mejora evidente del coste de las operaciones del TAD es implementar la tabla sobre unvector de manera que cada clave tenga asociada una y slo una posicin del vector. En otraspalabras, dada una funcin g : K Zn, donde Zn representa el intervalo cerrado [0, n-1], ysiendo n la cardinalidad del conjunto de claves, la tabla se puede representar con un vectorA tal que, dada una clave k, en A[g(k )] est el valor asociado a k ; si la clave est indefinida, enA[g(k )] residir el valor indefinido o, si no existe ningn valor que pueda desempear estepapel, en cada posicin aadiremos un campo booleano adicional que marque laindefinicin. Con esta representacin, es obvio que todas las operaciones se implementanen orden constante.

    La funcin g ha de ser inyectiva como mnimo; mejor an si es biyectiva, porque en casocontrario habr posiciones del vector que siempre quedarn sin usar. Incluso en el ltimocaso, sin embargo, el esquema es impracticable si n es muy grande y el porcentaje de clavesdefinidas es muy pequeo (lo que implica que la mayora de las posiciones del vector estarnsin usar), que es lo ms habitual.

    4.2.4 Implementacin por tablas de dispersin

    Las tablas de dispersin (ing., hashing table o tambin scatter table; to hash se traduce por"desmenuzar"), tambin conocidas como tablas de direccionamiento calculado , se parecenal esquema anterior en que utilizan una funcin h : K Zr que asigna un entero a una clave,pero ahora sin requerir que h sea inyectiva, lo que divide el dominio de las claves en r clasesde equivalencia y quedan dentro de cada clase todas las claves tales que, al aplicarles h, danel mismo valor. A h la llamamos funcin de dispersin (ing., hashing function), y los valores deZr son los valores de dispersin (ing., hashing values). Lo ms importante es que, a pesar dela no-inyectividad de h (que permite aprovechar mucho ms el espacio, porque slo seguardan los elementos definidos de la tabla), el coste de las operaciones se puede mantenerconstante con gran probabilidad.

    Como antes, se usa h(k ) para acceder a un vector donde residan las claves definidas juntocon su valor (ms otra informacin de gestin de la tabla que ya introduciremos). Como no serequiere que h sea inyectiva, se define el vector con una dimensin ms pequea que en elesquema anterior y se asigna cada clase de equivalencia a una posicin del vector. A lasposiciones del vector algunos autores las llaman cubetas (ing., bucket); esta terminologa seusa sobre todo al hablar de dispersin sobre memoria secundaria, donde en lugar devectores se usan ficheros.

    Quedan dos cuestiones por resolver:

    1 7 8 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • - Qu forma toma la funcin de dispersin? Ha de cumplir ciertas propiedades, comodistribuir bien las claves y ser relativamente rpida de calcular.

    - Qu pasa cuando en la tabla se quieren insertar claves con idntico valor dedispersin? Si primero se inserta una clave k, tal que h(k) = i, y despus otra k' , tal queh(k' ) = i, se dice que se produce una colisin (ing., collision), porque k' encuentraocupada la posicin del vector que le corresponde; a k y k' los llamamos sinnimos(ing., synonym). El tratamiento de las colisiones da lugar a varias estrategias deimplementacin de las tablas de dispersin.

    4.3 Funciones de dispersin

    En esta seccin se determinan algunos algoritmos que pueden aplicarse sobre una clavepara obtener valores de dispersin. Comencemos por establecer las propiedades que debecumplir una buena funcin de dispersin:

    - Distribucin uniforme: todos los valores de dispersin deben tener aproximadamenteel mismo nmero de claves asociadas dentro de K, es decir, la particin inducida por lafuncin da lugar a clases de equivalencia de tamao parecido.

    - Independencia de la apariencia de la clave: pequeos cambios en la clave han deresultar en cambios absolutamente aleatorios del valor de dispersin; adems, lasvariaciones en una parte de la clave no han de ser compensables con variacionesfcilmente calculables en otra parte.

    - Exhaustividad: todo valor de dispersin debe tener como mnimo una clave asociada,de manera que ninguna posicin del vector quede desaprovechada a priori.

    - Rapidez de clculo: los algoritmos han de ser sencillos y, si es necesario, programadosdirectamente en lenguaje mquina o bien en lenguajes como C, que permitenmanipulacin directa de bits; las operaciones necesarias para ejecutar los algoritmosdeben ser lo ms rpidas posible (idealmente, desplazamientos o rotaciones de bits,operaciones lgicas y similares).

    Es necesario sealar que la construccin de computadores cada vez ms potentes relativizala importancia del ltimo criterio al disear la funcin; ahora bien, sea cual sea la potencia declculo del computador parece seguro que operaciones como la suma, los desplazamientosy los operadores lgicos sern siempre ms rpidas que el producto y la divisin, porejemplo, y por esto seguimos considerando la rapidez como un parmetro de diseo.

    Por otro lado, y este es un hecho fundamental para entender la estrategia de dispersin, lasdos primeras propiedades se refieren al dominio potencial de claves K, pero no aseguran elbuen comportamiento de una funcin de dispersin para un subconjunto A de claves,

    Tablas 1 7 9__________________________________________________________________________________

  • siendo A K, porque puede ocurrir que muchas de las claves de A tengan el mismo valorde dispersin y que algunos de estos valores, por el contrario, no tengan ningunaantiimagen en A . Precisamente por esto, una funcin de dispersin tericamente buenapuede dar resultados malos en un contexto de uso determinado. Si se considera que esteproblema es grave, en las implementaciones de tablas que se introducen en la prximaseccin se puede aadir cdigo para controlar que el nmero de colisiones no sobrepaseuna cota mxima. En todo caso, esta propiedad indeseable de la dispersin siempre debeser tenida en cuenta al disear estructuras de datos.

    Es natural preguntarse, antes de empezar a estudiar estrategias concretas, quposibilidades existen de encontrar buenas funciones de dispersin. Si se quiere almacenarn claves en una tabla de dimensin r, hay r n posibles configuraciones, cada una de ellasobtenida mediante una hipottica funcin de dispersin; si n < r, slo r ! / (r - n)! de estasfunciones no provocan ninguna colisin. Por ejemplo, D.E. Knuth presenta la "paradoja delcumpleaos": si acuden veintitrs personas a una fiesta, es ms probable que haya dos deellas que celebren su cumpleaos el mismo da que no lo contrario [Knu73, pp. 506-507].Por ello, lo que pretendemos no es buscar funciones que no provoquen ninguna colisinsino funciones que, en el caso medio, no provoquen muchas4. Debe tenerse en cuenta,adems, que es tericamente imposible generar datos aleatorios a partir de claves noaleatorias, pero lo que se busca aqu es una buena simulacin, lo que s que es posible;incluso una funcin de dispersin puede provocar menos colisiones que una autnticafuncin aleatoria.

    Analizaremos las funciones de dispersin segn dos enfoques diferentes:

    - Considerando directamente las funciones h : K Zr .- Considerando las funciones h : K Zr como la composicin de dos funciones

    diferentes, f : K Z y g : Z Zr .

    Asimismo, a partir de ahora consideraremos que las claves son, o bien enteros dentro de unintervalo cualquiera, o bien cadenas de caracteres (las cadenas de caracteres lasdenotaremos por C*, siendo C el conjunto vlido de caracteres de la mquina), porque es elcaso usual. A veces, no obstante, nos encontramos con que las claves han de considerarsecomo la unin o composicin de diversos campos, y es necesario adaptar los algoritmos aquvistos a este caso, sin que generalmente haya mayores problemas. Cuando las claves seannumricas, la funcin h ser directamente igual a g.

    1 8 0 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    4 Si la tabla es esttica y las claves se conocen a priori, hay tcnicas que permiten buscar funcionesque efectivamente no generen sinnimos; son las denominadas tcnicas de dispersin perfecta (ing.,perfect hashing ), que no estudiamos aqu. Se puede consultar el articulo de T.G. Lewis y C.R. Cook"Hashing for Dynamic and Static Internal Tables" (publicado en el Computer IEEE, 21(10), 1988, es unresumen de la tcnica de dispersin en general) para una introduccin al tema y [Meh84] para unestudio en profundidad.

  • 4.3.1 Funciones de traduccin de cadenas a enteros

    Normalmente, las funciones f : C * Z interpretan cada carcter de la cadena como unnmero, y se combinan todos los nmeros as obtenidos mediante algn algoritmo;normalmente, estos nmeros son naturales entre 0 y 127 255, segn el cdigo concretode la mquina.

    El primer problema que se presenta es que seguramente las claves no usarn todos loscaracteres del cdigo. Por ejemplo, imaginemos el caso habitual en que las cadenas decaracteres representan nombres (de personas, de lugares, etc.); es evidente que algunoscaracteres vlidos del cdigo de la mquina no aparecern nunca (como '&', '%', etc.), por loque su codificacin numrica no se usar. Por ejemplo, si el cdigo de la mquina es ASCIIestndar (con valores comprendidos en el intervalo [0, 127]) y en los nombres slo aparecenletras y algunos caracteres especiales ms (como '.' y '-'), las cadenas estarn formadas por unconjunto de 54 caracteres del cdigo (suponiendo que se distingan maysculas deminsculas); es decir, ms de la mitad del cdigo quedar desaprovechado y por ello losvalores generados no sern equiprobables (es ms, puede haber valores inalcanzables).Para evitar una primera prdida de uniformidad en el tratamiento de la clave es convenienteefectuar una traduccin de cdigos mediante una funcin : C [1, b], siendo b el nmerodiferente de caracteres que pueden formar parte de las claves, de manera que la funcin deconversin f se define finalmente como f(k ) = f'((k )), siendo la extensin de a todoslos caracteres de la clave y f' una funcin f ' : [1, b]* Z. Esta conversin ser ms o menoslaboriosa segn la distribucin de los caracteres dentro del cdigo usado por la mquina.

    A continuacin, se definen algunes funciones f aplicables sobre una clave k = cn...c1 :

    - Suma de todos los caracteres: f(k ) = i:1 i n: (ci ). Empricamente se observa quelos resultados de esta funcin son poco satisfactorios, fundamentalmente porque elvalor de un carcter es el mismo independientemente de la posicin en que aparezca.

    - Suma ponderada de todos los caracteres: modifica la frmula anterior considerandoque los caracteres tienen un peso asociado que depende de la posicin dondeaparecen y queda: f(k ) = i : 1 i n: (ci ).b i-1. Los resultados son espectacularmentemejores que en el mtodo anterior y por ello esta funcin es muy usada, aunquepresenta dos problemas que es necesario conocer:

    Es ms ineficiente, a causa del clculo de una potencia y del posterior producto acada paso del sumatorio. La potencia se puede ahorrar introduciendo una tablaauxiliar T, tal que T[i] = b i-1, que se inicializar una nica vez al principio delprograma y que generalmente ser lo suficientemente pequea dentro delcontexto de los datos del programa como para despreciar el espacio que ocupa.Otra opcin consiste en elevar, no b, sino la menor potencia de 2 ms grande queb, de manera que los productos y las potencias puedan implementarse comooperaciones de desplazamiento de bits; los resultados de esta variante no son tan

    Tablas 1 8 1__________________________________________________________________________________

  • malos como se podra temer (dependiendo de la diferencia entre b y la potencia de2). Es necesario controlar, no obstante, que los desplazamientos no provoquenapariciones reiteradas de ceros dentro de los sumandos parciales ni aparicionescclicas de la misma secuencia de nmeros.

    Se puede producir un desbordamiento en el clculo, que es necesario detectarantes que se produzca; por ejemplo, si b vale 26, en una mquina que use 32 bitspara representar los enteros puede producirse desbordamiento a partir del octavocarcter. Una solucin consiste en ignorar los bits que se van generando fuera deltamao de la palabra del computador, pero esta estrategia ignora cierta cantidad deinformacin reduciendo pues la aleatoriedad de la clave; adems, no siempre esposible y/o eficiente detectar e ignorar este desbordamiento.Una alternativa es dividir la clave de entrada en m trozos de tamao k bits (exceptouno de ellos si el resto de la divisin m /k es diferente de cero) aplicando entoncesla frmula de la suma ponderada sobre cada uno de estos trozos y combinando losresultados parciales mediante sumas. El tamao k debe verificar que el clculo decada trozo por separado no provoque desbordamiento, y tampoco su suma final.Es decir, k es el mayor entero que cumple m.bk maxent, siendo maxent el mayorentero representable en la mquina.

    - Cualquier otra variante del mtodo anterior hecha a partir de estudios empricos,cuando se tiene una idea aproximada de las distribuciones de los datos. Por ejemplo,en el artculo "Selecting a Hashing Algorithm", de B.J. McKenzie, R. Harries y T. Bell,Software-Practice and Experience, 20(2), 1990, se presentan algunas funciones dedispersin usadas en la construccin de compiladores, que utilizan otros valores paraponderar los caracteres (potencias de dos, que son rpidas de calcular o nmerosprimos, que distribuyen mejor).

    4.3.2 Funciones de restriccin de un entero en un intervalo

    En la literatura sobre el tema se encuentran gran cantidad de funciones; destacamos tres:

    - Divisin (ing., division): definida como g(z) = z mod r, es muy sencilla y rpida de calcular.Evidentemente, el valor r es crucial para una buena distribucin. As, por ejemplo, unapotencia de 2 favorece la eficiencia, pero la distribucin resultante ser defectuosa,porque simplemente tomara los log2r bits menos significativos de la clave; tampoco esbueno que r sea par, porque la paridad de z se conservara en el resultado; ni que seaun mltiplo de 3, porque dos valores z y z' que slo se diferencien en la posicin dedos bytes tendran el mismo valor. En general, es necesario evitar los valores de r talesque (bs a) mod r = 0, porque un mdulo con este valor tiende a ser una superposicinde los bytes de z, siendo lo ms adecuado un r primo tal que b s mod r a, para s y apequeos. En la prctica, es suficiente que r no tenga divisores ms pequeos que20. Se puede encontrar un ejemplo de eleccin de r en [Meh84, pp. 121-122].

    1 8 2 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • - Desplegamiento-plegamiento (ing., folding-unfolding): se divide la representacinbinaria de la clave numrica z en m partes de la misma longitud k (excepto posiblementela ltima), normalmente un byte. A continuacin, se combinan las partes segn unaestrategia determinada (por ejemplo, con sumas o o-exclusivos) obtenindose un valorr = 2k+, dependiento del mtodo concreto de combinacin (por ejemplo, si usamoso-exclusivos, = 0).

    - Cuadrado: se define g(z) como la interpretacin numrica de los log2r bits centrales dez 2. Conviene que r sea potencia de 2. Esta funcin distribuye bien porque los bitscentrales de un cuadrado no dependen de ninguna parte concreta de la clave, siempreque no haya ceros de relleno al inicio o al final del nmero. Debe controlarse el posibledesbordamiento que pueda provocar el clculo del cuadrado. En [AHU83, p.131] sepresenta una variante interesante.

    El problema de todos estos mtodos es que pueden distribuir mal una secuencia dada declaves, en cuyo caso es necesario encontrar una nueva funcin, pero, con qu criterios? Ysi esta nueva funcin tampoco se comporta correctamente? Un enfoque diferente consisteen no trabajar con funciones individuales sino con familias de funciones de modo que, si unade estas funciones se comporta mal para una distribucin dada de la entrada, se elijaaleatoriamente otra de la misma familia; son las denominadas familias universales (ing.,universal class) de funciones de dispersin, definidas por J.L. Carter y M.N. Wegman el ao1979 en "Universal Classes of Hash Functions", Journal of Computer and System Sciences ,18. De manera ms formal, dada una familia de funciones H, H {g : Za Zr }, siendo a elvalor ms grande posible despus de aplicar f a la clave, diremos que H es una familia2-universal si: x,y : x,y Za: ||{h H / h(x) = h(y)}|| ||H|| / r o, en otras palabras, si ningn parde claves colisiona en exceso sobre el conjunto de todas las funciones de H (precisamente,el prefijo "2" de la definicin pone el nfasis en el estudio del comportamiento sobre paresde elementos), de manera que si se toma aleatoriamente una funcin de H y se aplica sobreun conjunto cualquiera de datos A , A K, es altamente probable que esta funcin sea tanbuena (es decir, que distribuya las claves igual de bien) como otra funcin diseadaespecficamente para K. Informalmente, se est afirmando que dentro de H hay un nmerosuficiente de "buenas" funciones para esperar que aleatoriamente se elija alguna de ellas.

    Una consecuencia de la definicin de 2-universal es que, dado un conjunto A cualquiera declaves y dada k K una clave cualquiera tal que k A , el nmero esperado de colisiones dek con las claves de A usando una funcin f de 2-universal est acotado por ||A || / r, que esuna magnitud que asegura el buen comportamiento esperado de la funcin. Es ms, lavariancia de este valor est acotada para cada consulta individual, lo que permite forzar quedichas consultas cumplan restricciones temporales (v. "Analysis of a universal class offunctions", G. Markowsky, J.L. Carter y M.N. Wegman, en los Proceedings 7th Workshop ofMathematical Foundations of Computer Science, LNCS 64, Springer-Verlag).

    Tablas 1 8 3__________________________________________________________________________________

  • Carter y Wegman presentan tres clases de 2-universal, adoptadas tambin por otros autores(principalmente las dos primeras, posiblemente con pequeas variaciones), que exhiben elmismo comportamiento y que pueden evaluarse de modo eficiente:

    - H1: sea p un nmero primo tal que p a, sea g1: Zp Zr una funcin uniformecualquiera (por ejemplo, g1(z) = z mod r ), sea g2m,n : Za Zp una familia de funcionestales que m,nZa, m 0, definida como g2 m,n(z) = (m.z + n) mod p y, finalmente, seagm,n: Za Zr una familia de funciones definida como gm,n(z) = g1(g2m,n(z)). Entoncesse cumple que la clase H1 = {gm,n / m,n Za con m 0} es 2-universal. El artculooriginal de Carter y Wegman da una propiedad sobre p que, si se cumple, reduce elnmero de divisiones del algoritmo de dispersin.

    - H3: en este caso, es necesario que r sea potencia de 2, r = 2s. Sea j = log2a (esdecir, j es el nmero mnimo de bits necesarios para codificar cualquier valor de Za),sea el conjunto de matrices Mjxs de componentes 0 1, sea gM: Za Zr unafamilia de funciones definida como gM(z) = [z js(mj1...mjs)] s...s [z1s(m11...m1s)],donde zj...z1 es la representacin binaria de z, M es una matriz de tal que sus filasson de la forma (mi1...mis), s es la operacin o-exclusivo sobre operandos de s bits yzis(mi1...mis) es igual a s ceros si zi = 0 o a (mi1...mis) si zi = 1. Entonces se cumpleque la clase H3 = {gM / M } es 2-universal.

    - H2: variante de H3 que permite ganar tiempo a costa de espacio, consiste en interpretarel nmero z = zj...z1 como un nmero en base que se puede expandir a otro nmerobinario z' = zjz(j)-1...z1, en el que habr exactamente r / unos; si esta magnitud noes muy grande, el clculo exige menos o-exclusivos.

    4.3.3 Funciones de traduccin de cadenas a enteros en un intervalo

    La estrategia ms sencilla consiste en combinar dos mtodos de las familias que acabamosde estudiar. El ms usual consiste en aplicar la funcin de suma ponderada a la cadena decaracteres que forma la clave y operar el resultado con una familia universal o un mtodoparticular, generalmente el mdulo; en este ltimo caso, se puede alternar el mdulo con lasuma ponderada de manera que se evite el problema del desbordamiento.

    Tambin se pueden disear funciones que manipulen directamente la representacinbinaria de la clave. Si la clave es pequea, puede interpretarse su representacin como unnmero binario, si no, es necesario combinar sus bytes con ciertas operaciones(usualmente, sumas y o-exclusivos) de manera parecida al mtodo de desplegamiento vistoen el punto anterior. Presentamos aqu uno de estos mtodos propuesto por P.K. Pearsonen "Fast Hashing of Variable-Length Text Strings", Communications ACM, 33(6), 1990, quetiene un funcionamiento parecido a la familia H3 de funciones universales.

    1 8 4 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • Sea r = 2s la mnima potencia de 2 ms grande que la base b de los caracteres, y sea unvector T [de 0 a r -1] que contenga una permutacin aleatoria (sin repeticiones) de loselementos de Zr (sus elementos, pues, se representan con s bits); la funcin de dispersinh : C* Zr aplicada sobre una clave k = cn...c1 se define como:

    A0(k) = 0Ai(k) = T[Ai-1(k) s (ci)]h(k) = An(k)

    siendo s la operacin o-exclusivo sobre operandos de s bits.

    Este esquema se adapta perfectamente al caso habitual de claves de longitud variable odesconocida a priori ; adems, no impone ninguna restriccin sobre su longitud y no necesitapreocuparse de posibles desbordamientos. Ahora bien, la definicin dada presenta un graveinconveniente: la dimensin de la tabla viene determinada por s, lo que normalmente resultaen un valor muy pequeo. Si suponemos el caso inverso, en el cual se disea la funcin dedispersin a partir de r, es necesario reformular la definicin. Sea t el entero ms pequeo talque r - 1 < 2 t, siendo t el nmero de bits necesario para representar valores dentro de Zr ;entonces, se aplica la funcin An un total de p = t /s veces sobre k de la siguiente forma:

    - Se define la familia de funciones Ani(k) = An(k i ), donde k i = cn...c1 i, siendo c i el carctertal que (c i ) = ((c)+i ) mod r.

    - Se define la funcin de dispersin como h(k) = An p-1(k) . ... . An0(k), siendo . laconcatenacin de bits. Notemos que, efectivamente, h(k) da valores en Zr.

    Se puede demostrar que las p secuencias de valores de Ani(k) son independientes entre s.Si r no es potencia de 2, la ltima vez que se aplique la funcin es necesario obtener menosde s bits, por ejemplo, tomando el resto de la divisin t / s.

    4.3.4 Caracterizacin e implementacin de las funciones de dispersin

    En la siguiente seccin se presentan varias implementaciones de tablas de dispersin.Todas ellas sern independientes de la funcin de dispersin elegida, que se convertir,pues, en un parmetro formal de la implementacin. Por ello, es necesario encapsular ladefinicin de las funciones de dispersin en un universo de caracterizacin.

    a) Caracterizacin de las funciones f : CCCC * ZZZZEn el universo de caracterizacin ELEM_DISP_CONV se define el gnero elem querepresenta las claves tal como se ha requerido en la especificacin. Adems, para poderaplicarles una funcin de dispersin, estas claves se consideran como una tira de elementosindividuales de tipo tem (que generalmente sern caracteres, como ya se ha dicho), ypresentan dos operaciones, una para averiguar el nmero de tems de una clave y otra paraobtener el tem i-simo. Por ltimo, es necesario definir tambin como parmetros formales la

    Tablas 1 8 5__________________________________________________________________________________

  • cardinalidad b del dominio de los tems y la funcin de conversin de un tem a un naturaldentro del intervalo apropiado, que denotamos respectivamente por base y conv. Eluniverso resultante contiene, pues, los parmetros formales necesarios para definir lasfunciones de dispersin f.

    universo ELEM_DISP_CONV caracterizausa ELEM_=, NAT, BOOLtipo temops i_simo: elem nat tem

    ntems: elem natconv: tem natbase: nat

    errores velem; inat: [NAT.ig(i, 0) (i > ntems(v))] i_simo(v, i)ecns vtem: (conv(v) > 0) (conv(v) base) = cierto

    funiverso

    Fig. 4.4: caracterizacin de las claves de las funciones de dispersin f.

    A continuacin, se puede enriquecer este universo formando uno nuevo, FUNCIONES_F,que aade la caracterizacin de las funciones f : C* Z. El resultado aparecer en lacabecera de todos aquellos universos parametrizados por la funcin de dispersin.

    universo FUNCIONES_F caracterizausa ELEM_DISP_CONV, NATops f: elem nat

    funiverso

    Fig. 4.5: enriquecimiento de ELEM_DISP_CONV aadiendo la funcin f.

    Los diferentes mtodos particulares se pueden definir dentro de universos deimplementacin convenientemente parametrizados por ELEM_DISP_CONV ; los algoritmosresultantes son parmetros reales potenciales en aquellos contextos donde se requiera unafuncin f (es decir, en universos parametrizados por FUNCIONES_F). Por ejemplo, en la fig.4.6 se muestra el mtodo de la suma ponderada aplicando la regla de Horner 5; se suponeque mult_desb(x, y, z) efectua la operacin x*y + z sin que se llegue a producir realmentedesbordamiento en ninguna operacin.

    1 8 6 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    5 Opcionalmente, y para reducir el nmero de universos de las bibliotecas de mdulos al mnimo, sepodran encapsular todos los mtodos de dispersin en un nico universo; la estructuracin aqupresentada se adapta fcilmente a este caso.

  • universo SUMA_POND (ELEM_DISP_CONV) es6usa NATfuncin suma_pond (v es elem) devuelve nat esvar acum, i son nat fvar

    acum := 0para todo i desde ntems(v) bajando hasta 1 hacer

    acum := mult_desb(acum, base, conv(i_simo(v, i)))fpara todo

    devuelve acumfuniverso

    Fig. 4.6: implementacin del mtodo de la suma ponderada.

    b) Caracterizacin de las funciones g : Z Z rEn el universo de caracterizacin FUNCIONES_G se definen el mtodo de dispersin y elvalor r (v. fig. 4.7). A continuacin, se pueden implementar los diversos algoritmos de formasimilar a las funciones f, parametrizados tan solo por el valor r (natural caracterizado enVAL_NAT); por ejemplo, en la fig. 4.8 se implementa el mtodo del mdulo.

    universo FUNCIONES_G caracterizausa NAT, BOOLops r: nat

    g: nat natecns znat: g(z) < r = cierto

    funiverso

    Fig. 4.7: caracterizacin de las claves de las funciones de dispersin g.

    universo DIVISIN (VAL_NAT) esusa NATfuncin divisin (z es nat) devuelve nat esdevuelve z mod val

    funiverso

    Fig. 4.8: implementacin del mtodo de la divisin.

    Tablas 1 8 7__________________________________________________________________________________

    6 En esta situacin no es til definir la especificacin inicial de la funcin, porque de hecho se estaradando su algoritmo, y por ello se implementa la funcin directamente. En todo caso, se podraconsiderar la posibilidad de especificarla en otro marco semntico.

  • c) Caracterizacin de las funciones h : C* ZrDe manera similar, se define un universo de caracterizacin parametrizado tanto por el tipo delas claves como por el valor r; este universo caracteriza todos los smbolos que el usuario deuna tabla de dispersin ha de determinar, y por ello es el universo que finalmente apareceren la cabecera de las diversas implementaciones de las tablas.

    universo CLAVE_DISPERSIN caracterizausa ELEM_=, VAL_NAT, NAT, BOOLops h: elem natecns velem: h(v) < val = cierto

    funiverso

    Fig. 4.9: caracterizacin de las claves en el contexto de las funciones de dispersin h.

    En este punto ya es posible definir funciones de dispersin. La primera opcin consiste encomponer dos funciones f y g, tal como se muestra en la fig. 4.10. Por ejemplo, en la fig.4.11 se da la composicin de la suma ponderada y la divisin; las dos primeras instancias,privadas, construyen las funciones f y g en el contexto requerido por el tipo de las claves, elvalor del mdulo, etc. y, a continuacin, se efecta una tercera instancia que define lafuncin h simplemente como composicin de las anteriores, con un renombramiento final.

    universo COMPOSICIN_F_Y_G (FUNCIONES_F, FUNCIONES_G) esfuncin g_de_f (v es elem) devuelve nat esdevuelve g(f(v))

    funiverso

    Fig. 4.10: composicin de funciones de dispersin.

    La segunda manera de definir funciones de dispersin consiste en trabajar directamentesobre la clave sin pasos intermedios. Por ejemplo, en la fig. 4.12 se muestra la funcin quealterna la suma ponderada y la divisin.

    Cabe destacar que los universos resultantes de estos pasos deben crearse una nica vez yque, a partir de ese momento, el usuario los tendr disponibles en una biblioteca defunciones de dispersin, y se limitar simplemente a efectuar las instancias adecuadas enaquellos contextos que lo exijan (sin necesidad de conocer la estructuracin interna enuniversos). Estas instancias son muy simples. Por ejemplo, supongamos que se quiereimplementar una tabla donde las claves son cadenas de letras maysculas, los valores son

    1 8 8 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • enteros y la dimensin de la tabla es 1017 y que, como funcin de dispersin, se componenla suma ponderada y la divisin; el resultado podra ser la instancia:

    instancia SUMA_POND_Y_DIV(ELEM_DISP_CONV, VAL_NAT) dondeelem es cadena, tem es carcter= es CADENA.=, ntems es CADENA.long, i_simo es CADENA.i_simoconv es conv_maysc, base es 26, val es 1017

    donde conv_maysc debera estar implementada en la biblioteca de funciones deconversin, y CADENA presenta las operaciones definidas en el apartado 1.5.1.

    universo SUMA_POND_Y_DIV(E es ELEM_DISP_CONV, V1 es VAL_NAT) esinstancia privada SUMA_POND(F es ELEM_DISP_CONV) donde

    F.elem es E.elem, F.tem es E.temF.= es E.=, F.ntems es E.tems, F.i_simo es G.i_simoF.conv es E.conv, F.base es E.base

    instancia privada DIVISIN(V2 es VAL_NAT) donde V2.val es V1.valinstancia COMPOSICIN_F_Y_G(F es FUNCIONES_F, G es FUNCIONES_G) donde

    F.elem es E.elem, F.tem es E.temF.= es E.=, F.ntems es E.tems, F.i_simo es G.i_simoF.conv es E.conv, F.base es E.base, F.f es suma_pondG.r es V1.val, G.g es divisin

    renombra f_de_g por suma_pond_y_divfuniverso

    Fig. 4.11: composicin de la suma ponderada y la divisin.

    universo SUMA_POND_Y_DIV_SIMULT(ELEM_DISP_CONV, VAL_NAT) esusa NATfuncin suma_pond_y_mod (v es elem) devuelve nat esvar acum, i son nat fvar

    acum := 0para todo i desde ntems(v) bajando hasta 1 hacer

    acum := mult_desb(acum, base, conv(i_simo(v, i))) mod valfpara todo

    devuelve acumfuniverso

    Fig. 4.12: aplicacin simultnea de la suma ponderada y la divisin.

    Tablas 1 8 9__________________________________________________________________________________

  • 4.4 Organizaciones de las tablas de dispersin

    Las tablas de dispersin pueden organizarse de varias maneras segn el modo en que segestionen las colisiones. Hay dos grandes familias de tablas dependiendo de si seencadenan o no los sinnimos; adems, existen diversas organizaciones que soncombinaciones de estas dos, de las que se presentar una. Algunos autores tambinclasifican las tablas como abiertas o cerradas, segn exista una zona diferenciada para todaso parte de las claves, o se almacenen en un nico vector directamente indexado por lafuncin de dispersin. A veces, los nombres que dan diversos autores pueden causarconfusin; por ejemplo, algunos textos denominan closed hashing a una organizacin queotros llaman open addressing.

    A lo largo de la seccin, definiremos las diferentes organizaciones como universosparametrizados por los gneros de las claves y los valores, la igualdad de las claves, el valorindefinido, la funcin de dispersin y el nmero de valores de dispersin diferentes que hay.

    4.4.1 Tablas encadenadas

    En las tablas de dispersin encadenadas (ing., chaining) se forman estructuras lineales conlos sinnimos; estudiaremos dos esquemas:

    - Tablas encadenadas indirectas: se encadenan los sinnimos de un mismo valor dedispersin, y el primer elemento de la lista es accesible a partir de un vector ndice.

    - Tablas encadenadas directas: se guarda la primera clave de cada valor de dispersin,con su valor asociado, en una zona diferenciada, y los respectivos sinnimos seencadenan en otra zona, y son accesibles a partir del que reside en la zonadiferenciada.

    a) Tablas encadenadas indirectasTambin conocidas con el nombre de tablas encadenadas abiertas (ing., separate chaining),asocian la lista de los sinnimos de un valor de dispersin i a la posicin i de un vector ndice.En la fig. 4.13 se muestra el esquema de esta representacin. Queda claro que si la funcindistribuye correctamente, las listas de sinnimos sern de longitud similar, concretamenten /r , donde n es el nmero de elementos en la tabla. En consecuencia, tenemos que[Sn(k)] = (n+r )/2r y [Sn(k)] = n /r , de manera que las operaciones quedan (n /r). Siaproximadamente hay tantos elementos como valores de dispersin, este cociente ser muypequeo (es decir, las listas de sinnimos sern muy cortas) y las funciones del TAD sepodrn considerar (1), dado que la insercin y la supresin consisten simplemente enbuscar el elemento y modificar un par de encadenamientos.

    1 9 0 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • 01

    r-1

    k01 k02 0k p0v01 v02

    ...

    0v p0

    iZ .jZ : h(k ) = ir p i i j+1.

    .

    .

    Fig. 4.13: organizacin de una tabla de dispersin encadenada indirecta.

    En la fig. 4.14 se presenta una posible implementacin para el tipo tabla representando laslistas en memoria dinmica (otra alternativa sera guardar los elementos dentro de un nicovector compartido por todas las listas). Observemos que el invariante establece que loselementos de la lista que cuelga de la posicin i del vector ndice son sinnimos del valor dedispersin i, usando la conocida funcin que devuelve la cadena que cuelga de un elementoinicial (v. fig. 3.23); cualquier otra propiedad de la representacin se deduce de sta. Losuniversos de caracterizacin definen los parmetros necesarios para aplicar las funciones dedispersin tal como se ha explicado en la seccin anterior; notemos que, en particular, todoslos parmetros que aparecan en la especificacin tambin estn en la implementacin,aunque el encapsulamiento en universos de caracterizacin haya cambiado. Tanto en estaimplementacin como en posteriores, no se incluye cdigo para controlar el buencomportamiento de la funcin de dispersin, quedando como ejercicio para el lector.

    Los algoritmos son claros: se accede por la funcin de dispersin al ndice y se recorre la listade sinnimos; las inserciones se efectan siempre por el inicio (es lo ms sencillo) y en lassupresiones es necesario controlar si el elemento borrado es el primero, para actualizar elndice (una alternativa sera utilizar elementos fantasmas, pero a costa de aumentarconsiderablemente el espacio empleado, siendo necesario un estudio cuidadoso sobre suconveniencia). Se usa una funcin auxiliar, busca, que busca una clave en la tabla.Podramos considerar la posibilidad de ordenar los sinnimos por clave o de disponer delistas autoorganizativas; estas dos variantes reducen el coste de algunas bsquedas a costade las inserciones. Otra variante consiste en no buscar las claves cuando se insertan sinoaadirlas directamente al inicio de la lista, de manera que la consulta siempre encontrarprimero la ltima ocurrencia de la clave; obviamente, si las claves se redefinen con frecuenciapuede llegar a desaprovecharse mucha memoria.

    Una alternativa a la implementacin del tipo consiste en usar una instancia de algunaimplementacin de las listas escrita en algn universo ya existente. En la seccin 6.2.2 seintroduce una representacin del TAD de los grafos usando listas con punto de inters quesigue esta estrategia, de manera que el lector puede comparar los resultados.

    Tablas 1 9 1__________________________________________________________________________________

  • universo TABLA_IND_PUNTEROS(CLAVE_DISPERSIN, ELEM_ESP) esimplementa TABLA(ELEM_=, ELEM_ESP)usa ENTERO, BOOLrenombra CLAVE_DISPERSIN.elem por clave, CLAVE_DISPERSIN.val por r

    ELEM_ESP.elem por valor, ELEM_ESP.esp por indeftipo tabla es vector [de 0 a r-1] de ^nodo ftipotipo privado nodo es

    tuplak es clave; v es valor; enc es ^nodo

    ftuplaftipoinvariante (T es tabla): i: 0 i r-1:

    NULOcadena(T[i]) (p: pcadena(T[i])-{NULO}: h(p^.k) = i)funcin crea devuelve tabla esvar i es nat; t es tabla fvar

    para todo i desde 0 hasta r-1 hacer t[i] := NULO fpara tododevuelve tfuncin asigna (t es tabla; k es clave; v es valor) devuelve tabla esvar i es nat; encontrado es booleano; ant, p son ^nodo fvar

    i := h(k); := busca(t[i], k)si encontrado entonces p^.v := v {cambiamos la informacin asociada}si no {es necesario crear un nuevo nodo}

    p := obtener_espaciosi p = NULO entonces error {no hay espacio}si no p^ := ; t[i] := p {insercin por el inicio}fsi

    fsidevuelve tfuncin borra (t es tabla; k es clave) devuelve tabla esvar i es nat; encontrado es booleano; ant, p son ^nodo fvar

    i := h(k); := busca(t[i], k)si encontrado entonces {es necesario distinguir si es el primero o no}

    si ant = NULO entonces t[i] := p^.enc si no ant^.enc := p^.enc fsiliberar_espacio(p)

    fsidevuelve t

    Fig. 4.14: implementacin de las tablas de dispersin encadenadas indirectas.

    1 9 2 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • funcin consulta (t es tabla; k es clave) devuelve valor esvar encontrado es booleano; res es valor; ant, p son ^nodo fvar

    := busca(t[h(k)], k)si encontrado entonces res := p^.v si no res := indef fsi

    devuelve res

    {Funcin busca(q, k) : busca el elemento de clave k apartir de q. Devuelve un booleano indicando el resultado de la bsqueda y, encaso de xito, un apuntador p a la posicin que ocupa y otro ant a supredecesor, que valdr NULO si p es el primero

    P ciertoQ encontrado r: rcadena(q)-{NULO}: r^.k = k

    encontrado p^.k = k (p = q ant = NULO p q ant^.enc = p) }funcin privada busca (q es ^nodo; k es clave) ret esvar encontrado es booleano; res es valor; ant, p es ^nodo fvar

    ant := NULO; p := q; encontrado := falsomientras (p NULO) encontrado hacer

    si p^.k = k entonces encontrado := cierto si no ant := p; p := p^.enc fsifmientras

    devuelve funiverso

    Fig. 4.14: implementacin de las tablas de dispersin encadenadas indirectas (cont.).

    Por ltimo, es necesario destacar un problema que surge en todas las organizaciones dedispersin: la progresiva degeneracin que sufren a medida que crece su tasa deocupacin. Para evitarla se puede incorporar a la representacin un contador de elementosen la tabla, de forma que el clculo de la tasa de ocupacin sea inmediato; al insertar nuevoselementos es necesario comprobar previamente que la tasa no sobrepase una cota mximadeterminada (valor que sera un parmetro ms, tanto de la especificacin como de laimplementacin), en cuyo caso se da un error conocido con el nombre de desbordamiento(ing., overflow ; evidentmente, la especificacin de las tablas debera modificarse para reflejareste comportamiento). La solucin ms simple posible del desbordamiento (y tambin la msineficiente) consiste en redimensionar la tabla, lo cual obliga a definir una nueva funcin dedispersin y, a continuacin reinsertar todos los identificadores presentes en la tabla usandoesta nueva funcin. Alternativamente, hay mtodos que aumentan selectivamente el tamaode la tabla, de manera que slo es necesario reinsertar un subconjunto de los elementos;estas estrategias, denominadas incrementales, difieren en la frecuencia de aplicacin y en laparte de la tabla tratada. Entre ellas destacan la dispersin extensible y la dispersin lineal ;ambos mtodos son especialmente tiles para tablas implementadas en disco y no seestudian aqu. En el apartado 4.4.6 se presentan unas frmulas que determinan

    Tablas 1 9 3__________________________________________________________________________________

  • exactamente como afecta la tasa de ocupacin a la eficiencia de las operaciones.

    b) Tablas encadenadas directasTambin conocidas con el nombre de tablas con zona de excedentes (ing., hashing withcellar ), precisamente por la distincin de la tabla en dos zonas: la zona principal de rposiciones, donde la posicin i, o bien est vaca, o bien contiene un par que cumpleh(k) = i, y la zona de excedentes, que ocupa el resto de la tabla y guarda las claves sinnimas.Cuando al insertar un par se produce una colisin, el nuevo sinnimo se almacena en estazona; todos los sinnimos de un mismo valor estn encadenados y el primer sinnimoresidente en la zona de excedentes es accesible a partir del que reside en la zona principal.Alternativamente, la zona de excedentes se puede implementar por punteros sin que elesquema general vare sustancialmente. Las consideraciones sobre la longitud de las listas yel coste de las operaciones son idnticas al caso de tablas encadenadas indirectas; slo esnecesario notar el ahorro de un acceso a causa de la inexistencia del vector ndice, queevidentmente no afecta al coste asinttico de las operaciones7.

    ZONA PRINCIPAL ZONA DE EXCEDENTES

    k01v01

    0k p0

    ...

    0 r-1 mx-1k02v02 0

    v p0

    iZ .jZ : h(k ) = ir p i i j+1Fig. 4.15: organizacin de una tabla de dispersin encadenada directa.

    En la fig. 4.16 se ofrece una implementacin del tipo; en la cabecera aparece un universo decaracterizacin adicional, VAL_NAT, para determinar la dimensin de la zona de excedentes(diversos estudios empricos apuntan a que esta zona ha de representar aproximadamenteel 14% de la tabla). Vuelve a haber una funcin auxiliar para buscar una clave. Observamosque la codificacin de las funciones es un poco ms complicada que en el caso indirecto,porque es necesario distinguir si una posicin est libre o no y tambin el acceso a la zonaprincipal del acceso a la zona de excedentes. Para solucionar el primer punto, se estableceuna convencin para saber si las posiciones de la zona principal estn o no vacas, y en casode que estn vacas, se marcan (es decir, se da un valor especial a algn campo; en la fig.4.16, se asigna el valor -2 al campo de encadenamiento, y queda el -1 para el ltimoelemento de cada lista). Las posiciones libres de la zona de excedentes se gestionan enforma de pila.

    1 9 4 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

    7 Pero que es importante al considerar la implementacin sobre ficheros; de hecho, las tablas con zonade excedentes son tiles sobre todo en memoria secundaria, donde en cada cubeta se colocan tantoselementos como se puedan leer del / escribir al disco en una nica operacin de entrada/salida.

  • La supresin de los elementos residentes en la zona de excedentes requiere tan slomodificar encadenamientos e insertar la posicin liberada en la pila de sitios libres; ahorabien, al borrar un elemento residente en la zona principal no se puede simplemente marcarcomo libre la posicin correspondiente, porque el algoritmo de bsqueda no encontrara loshipotticos sinnimos residentes en la zona de excedentes. As, se podra introducir otramarca para distinguir las posiciones borradas, de manera que las posiciones marcadas comoborradas se interpreten como ocupadas al buscar y como libres al insertar una nueva clave. Elmantenimiento de posiciones borradas puede provocar que en la zona principal existandemasiados "agujeros", que la tabla degenere (es decir, que empeore el tiempo de acceso) yque, eventualmente, se llene la zona de excedentes, quedando posiciones borradas en lazona principal y provocando error en la siguiente colisin. Una alternativa consiste en moverun sinnimo de la zona de excedentes a la zona principal, lo que es costoso si el tamao delos elementos es grande o si los elementos de la tabla son apuntados por otras estructurasde datos; en la fig. 4.16 se implementa esta ltima opcin y la primera queda como ejercicio.Notemos que el invariante es ciertamente exhaustivo y comprueba los valores de losencadenamientos, la ocupacin de las diferentes posiciones de la tabla y que todoselementos que cuelgan de la misma cadena sean sinnimos del valor correspondiente dedispersin; se usa la funcin cadena de la fig. 3.20 aplicada sobre tablas.

    universo TABLA_DIRECTA(CLAVE_DISPERSIN, ELEM_ESP, VAL_NAT) esimplementa TABLA(ELEM_=, ELEM_ESP)usa ENTERO, BOOLrenombra CLAVE_DISPERSIN.elem por clave, CLAVE_DISPERSIN.val por r

    ELEM_ESP.elem por valor, ELEM_ESP.esp por indefconst mx vale r+VAL_NAT.valtipo tabla es tupla A es vector [de 0 a mx-1] de

    tupla k es clave; v es valor; enc es entero ftuplasl es entero

    ftuplaftipoinvariante (T es tabla): (T.sl = -1) (r T.sl < mx)

    i: 0 i r-1: (T.A[i].enc = -1) (T.A[i].enc = -2) (r T.A[i].enc < mx) i: r i mx-1: (T.A[i].enc = -1) (r T.A[i].enc < mx) i: 0 i r-1 T.A[i].enc -2: {es decir, para toda posicin no vaca}

    p: p(cadena(T.A, i) - {-1}): h(T.A[p].k) = i j: 0 j r-1 j i A[j].enc -2: cadena(T.A, i) cadena(T.A, j) = {-1} cadena(T.A, i) cadena(T.A, T.sl) = {-1}

    ( i: 0 i r-1 T.A[i].enc -2: cadena(T.A, T.A[i].enc) ) cadena(T.A, T.sl) = [r, mx-1] {-1}

    Fig. 4.16: implementacin de las tablas de dispersin encadenadas directas.

    Tablas 1 9 5__________________________________________________________________________________

  • funcin crea devuelve tabla esvar i es nat; t es tabla fvar

    {zona principal vaca}para todo i desde 0 hasta r-1 hacer t.A[i].enc := -2 fpara todo

    {pila de sitios libres}para todo i desde r hasta mx-2 hacer t.A[i].enc := i+1 fpara todot.A[mx-1].enc := -1; t.sl := r

    devuelve t

    funcin asigna (t es tabla; k es clave; v es valor) devuelve tabla esvar ant, i, p son enteros; encontrado es booleano fvar

    i := h(k)si t.A[i].enc = -2 entonces t.A[i] := {posicin libre en la zona principal}si no {hay sinnimos}

    := busca(t, k, i)si encontrado entonces t.A[p].v := v {cambio de la informacin asociada}si no si t.sl = -1 entonces error {no hay espacio}

    si no {se deposita en la zona de excedentes}p := t.sl; t.sl := t.A[t.sl].enct.A[p] := ; t.A[i].enc := p

    fsifsi

    fsidevuelve t

    funcin borra (t es tabla; k es clave) devuelve tabla esvar ant, p, nuevo_sl son enteros; encontrado es booleano fvar

    := busca(t, k, h(k))si encontrado entonces

    si ant = -1 entonces {reside en la zona principal}si t.A[p].enc = -1 entonces t.A[p].enc := -2 {no tiene sinnimos}si no {se mueve el primer sinnimo a la zona principal}

    nuevo_sl := t.A[p].enc; t.A[p] := t.A[t.A[p].enc]t.A[nuevo_sl].enc := t.sl; t.sl := nuevo_sl

    fsisi no {reside en la zona de excedentes}

    t.A[ant].enc := t.A[p].enc; t.A[p].enc := t.sl; t.sl := pfsi

    fsidevuelve t

    Fig. 4.16: implementacin de las tablas de dispersin encadenadas directas (cont.).

    1 9 6 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • funcin consulta (t es tabla; k es clave) devuelve valor esvar encontrado es booleano; res es valor; ant, p son entero fvar

    := busca(t, k, h(k))si encontrado entonces res := t.A[p].v si no res := indef fsi

    devuelve res

    {Funcin auxiliar busca(t, k, i): v. fig. 4.14}funcin privada busca (t es tabla; k es clave; i es entero) dev esvar encontrado es booleano; ant es entero fvar

    encontrado := falsosi t.A[i].enc -2 entonces {si no, no hay claves con valor de dispersin i }

    ant := -1mientras (i -1) encontrado hacer

    si t.A[i].k = k entonces encontrado := cierto si no ant := i; i := t.A[i].enc fsifmientras

    fsidevuelve

    funiverso

    Fig. 4.16: implementacin de las tablas de dispersin encadenadas directas (cont.).

    4.4.2 Tablas de direccionamiento abierto

    En las tablas de direccionamiento abierto (ing., open addresing) se dispone de un vector contantas posiciones como valores de dispersin; dentro del vector, no existe una zonadiferenciada para las colisiones ni se encadenan los sinnimos, sino que para cada clave sedefine una secuencia de posiciones que determina el lugar donde ir. Concretamente, alinsertar el par en la tabla, tal que h(k) = p0, se sigue una secuencia p0, ..., p r-1 asociadaa k hasta que:

    - Se encuentra una posicin ps tal que su clave es k : se sustituye su informacinasociada por v .

    - Se encuentra una posicin ps que est libre: se coloca el par . Si s 0, a la clavese la llama invasora (ing., invader), por razones obvias.

    - Se explora la secuencia sin encontrar ninguna posicin que cumpla alguna de las doscondiciones anteriores: significa que la tabla est llena y no se puede insertar el par.

    El esquema al borrar y consultar es parecido: se siguen las p0, , pr-1 hasta que se encuentrala clave buscada o una posicin libre. El punto clave de esta estrategia es que la secuenciap0, , pr-1 asociada a una clave k es fija durante toda la existencia de la tabla de manera que,cuando se aade, se borra o se consulta una clave determinada siempre se examinan las

    Tablas 1 9 7__________________________________________________________________________________

  • mismas posiciones del vector en el mismo orden; idealmente, claves diferentes tendrnasociadas secuencias diferentes. Este proceso de generacin de valores se denominaredispersin (ing., rehashing), la secuencia generada por una clave se denomina camino(ing., path o probe sequence) de la clave, y cada acceso a la tabla mediante un valor delcamino se denomina ensayo (ing., probe). En otras palabras, el concepto de redispersinconsiste en considerar que, en vez de una nica funcin h de dispersin, disponemos deuna familia {hi} de funciones, denominadas funciones de redispersin (ing., rehashingfunction), tales que, si la aplicacin de hi(k) no lleva al resultado esperado, se aplica hi+1(k); eneste esquema, la funcin h de dispersin se define como h0.

    Una vez ms es necesario estudiar cuidadosamente el problema de la supresin deelementos. La situacin es parecida a la organizacin encadenada directa: cuando una clavek colisiona y se deposita finalmente en la posicin determinada por un valor hj(k) se debe aque todas las posiciones determinadas por los valores hi(k), para todo i < j, estn ocupadas;al suprimir una clave que ocupa la posicin hs(k), para algn s < j, no se puede simplementemarcar como libre esta posicin, porque una bsqueda posterior de k dentro la tablasiguiendo la estrategia antes expuesta fracasara. En consecuencia, adems de lasposiciones "ocupadas" o "libres", se distingue un tercer estado que identifica las posiciones"borradas", las cuales se tratan como libres al buscar sitio para insertar un nuevo par y comoocupadas al buscar una clave; al igual que en el esquema encadenado directo ya estudiado,las posiciones borradas provocan la degeneracin de la tabla.

    En la fig. 4.17 se muestra una implementacin de las tablas de direccionamiento abierto sindeterminar la familia de funciones de dispersin, que queda como parmetro formal de laimplementacin sustituyendo a la tradicional funcin de dispersin; los universos decaracterizacin de los parmetros formales aparecen en la fig. 4.18. En la definicin de losvalores se aade una constante como parmetro formal, que permite implementar elconcepto de posicin borrada sin emplear ningn campo adicional (las posiciones librescontendrn el valor indefinido, que ya es un parmetro formal). En caso de no poderseidentificar este nuevo valor especial, se podra identificar una clave especial y, si tampocofuera posible, sera imprescindible entonces un campo booleano en las posiciones delvector que codificara el estado. Por lo que respecta al invariante, se usa una funcin auxiliarpredecesores que, para una clave k residente en la posicin pi de su camino, devuelve elconjunto de posiciones {p0, ..., pi-1}, las cuales, dada la estrategia de redispersin, nopueden estar libres. Por ltimo, la funcin auxiliar busca da la posicin donde se encuentrauna clave determinada o, en caso de no encontrarse, la primera posicin libre o borradadonde se insertara, y da prioridad a las segundas para reaprovecharlas; el valor booleanodevuelto indica si la clave se ha encontrado o no. Como siempre, se podra incorporar uncontador para controlar el factor de ocupacin de la tabla, considerando que una posicinborrada vale lo mismo que una posicin ocupada.

    1 9 8 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • universo TABLA_ABIERTA(CLAVE_REDISPERSIN, ELEM_2_ESP_=)implementa TABLA(ELEM_=, ELEM_ESP)renombra

    CLAVE_REDISPERSIN.elem por clave, CLAVE_REDISPERSIN.val por rELEM_2_ESP_=.elem por valorELEM_2_ESP_=.esp1 por indef, ELEM_2_ESP_=.esp2 por borrada

    usa ENTERO, BOOLtipo tabla es vector [de 0 a r-1] de tupla k es clave; v es valor ftupla ftipoinvariante (T es tabla):

    i: 0 i r-1 T[i].v indef T[i].v borrada:j: jpredecesores(T, T[i].k, 0): T[j].v indef

    donde se define predecesores: tabla clave nat P(nat) como:T[h(i, k)].k = k predecesores(T, k, i) = T[h(i, k)].k k predecesores(T, k, i) = {h(i, k)} predecesores(T, k, i+1)

    funcin crea devuelve tabla esvar i es nat; t es tabla fvar

    para todo i desde 0 hasta r-1 hacer t[i].v := indef fpara tododevuelve t

    funcin asigna (t es tabla; k es clave; v es valor) devuelve tabla esvar p es entero; encontrado es booleano fvar

    := busca(t, k)si encontrado entonces t[p].v := v {se cambia la informacin asociada}si no {se inserta en la posicin que le corresponde}

    si p = -1 entonces error {no hay espacio} si no t[p] := fsifsi

    devuelve t

    funcin borra (t es tabla; k es clave) devuelve tabla esvar p es nat; encontrado es booleano fvar

    := busca(t, k)si encontrado entonces t[p].v := borrada fsi

    devuelve t

    funcin consulta (t es tabla; k es clave) devuelve valor esvar encontrado es booleano; res es valor; p es nat fvar

    := busca(t, k)si encontrado entonces res := t[p].v si no res := indef fsi

    devuelve res

    Fig. 4.17: implementacin de las tablas de dispersin abiertas.

    Tablas 1 9 9__________________________________________________________________________________

  • {Funcin auxiliar busca(t, k) : busca la clave k dentro de t ; si laencuentra, devuelve cierto y la posicin que ocupa, si no, devuelve falso y laposicin donde insertarla si es el caso (o -1, si no hay ninguna)

    P ciertoQ encontrado i: 0 i r-1: (t[i].k = k t[i].v indef t[i].v borrada)

    (encontrado t[j].k = k) (encontrado (t[j].v = indef t[j].v = borrada)

    t[j].v = indef ( s: spos(T, k, j): T[s].v = borrada)donde pos devuelve las posiciones del camino asociado a k entre T[h(0, k)] y j }

    funcin privada busca (t es tabla; k es clave) devuelve esvar i, p, j son enteros; encontrado, final son booleano fvar

    i := 0; final := falso; j := -1; encontrado := falsomientras (i r) final hacer

    p := h(i, k) {se aplica la funcin i-sima de la familia de redispersin}opcin

    caso t[p].v = indef hacer {la clave no est}final := cierto; si j = -1 entonces j := p fsi

    caso t[p].v = borrada hacer i := i + 1; j := p {sigue la bsqueda}en cualquier otro caso {la posicin contiene una clave definida}

    si t[p].k = k entonces encontrado := cierto; final := cierto si no i := i + 1 fsifopcin

    fmientrasdevuelve

    Fig. 4.17: implementacin de las tablas de dispersin abiertas (cont.).

    universo ELEM_2_ESP_= caracteriza universo CLAVE_REDISPERSIN caracterizausa BOOL usa ELEM, VAL_NAT, NAT, BOOLtipo elem ops h: nat elem nat {h(i, k) hi(k)}ops esp1, esp2: elem errores kelem; inat: [i > val] h(i, k)

    _=_, __: elem elem bool ecns kelem; inat: h(i, k) < val = ciertofuniverso funiverso

    Fig. 4.18: caracterizacin de los parmetros formales de las tablas de dispersin abiertas.

    En la fig. 4.19 se presenta la evolucin de una tabla de direccionamiento abierto siguiendo laestrategia de la fig. 4.17, suponiendo que las funciones de redispersin {hi} simplementetoman el ltimo dgito de la cadena y le suman i (es un buen ejemplo de estrategia quenunca debe emplearse!); la disposicin de los elementos dentro de la tabla depende de lahistoria y de los algoritmos concretos de insercin y de supresin, de manera que podramosencontrar otros configuraciones igualmente vlidas al cambiar cualquiera de los dos factores.

    2 0 0 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • LIB i0 i1

    i0 i1 v0

    BOR i1 v0 i2

    j0 BOR v0 i2

    BOR i1 v0

    j0 i1 v0 i2

    j0 k0 v0 i2

    Tabla inicial Insercin de i0 e i1

    Insercin de v0 : colisin Supresin de i0 : marcaje

    Insercin de i2 : colisin Insercin de j0 : reaprovechamiento

    Supresin de i1: marcaje Insercin de k0 : colisin y reaprovechamiento

    LIB LIB LIB LIB LIB LIB LIB

    LIB LIB

    LIB

    LIB LIB

    LIB

    LIB LIB

    Fig. 4.19: evolucin de una tabla de direccionamiento abierto(LIB: posicin libre; BOR: posicin borrada).

    Una vez formulada la implementacin genrica de las tablas de dispersin dedireccionamiento abierto, es necesario estudiar los diferentes mtodos de redispersinexistentes; todos ellos se definen a partir de una o dos funciones de redispersin primariasque denotaremos por h y h'. Sea cual sea la familia de funciones de redispersin elegida,hay tres propiedades que deberan cumplirse; la primera de ellas, sobre todo, es ineludible:

    - Para una clave dada, su camino es siempre el mismo (las mismas posiciones en elmismo orden).

    - Para una clave dada, su camino pasa por todas las posiciones de la tabla.

    - Para dos claves diferentes k y k', si bien es prcticamente inevitable que a vecescompartan parte de sus caminos, es necesario evitar que estos caminos sean idnticosa partir de un punto determinado.

    Examinemos atentamente esta ltima propiedad. De entrada parece factible que se cumpla,porque en una tabla de dimensin r hay r ! caminos diferentes de longitud r; ahora bien, lamayora de esquemas de redispersin que estudiamos a continuacin usan bastante menosde r ! caminos y, por ello, presentan normalmente el fenmeno conocido como apiamiento(ing., clustering), que consiste en la formacin de largas cadenas de claves en la tabla(denominadas cadenas de reubicacin) que la hacen degenerar rpidamente. Imaginemos elcaso extremo en que k,k'K: hi(k) = hj(k') hi+d(k) = hj+d(k'), y sea una secuencia p1, , ps

    Tablas 2 0 1__________________________________________________________________________________

  • de posiciones ocupadas por claves k1, , ks, tal que i: 2 i s : j : 1 j i : h(ki) = pj yh(k1) = p1 ; en esta situacin, la insercin de una nueva clave k, tal que i: 1 i s : h(k) = p i,implica que la posicin destino de k es hs+1(k1); es decir, la posicin hs+1(k1) es el destino degran cantidad de claves, por lo que su posibilidad de ocupacin es muy grande; adems, elefecto es progresivo puesto que, cuando se ocupe, la primera posicin hs+1+x (k1) libretendr todava ms probabilidad de ocupacin que la que tena hs+1(k1).

    A continuacin, enumeramos los principales mtodos de redispersin.

    a) Redispersin uniformeMtodo de inters principalmente terico, que asocia a cada clave un camino distinto, que esuna permutacin de los elementos de Zr ; los r ! caminos posibles son equiprobables.Evidentemente, es el mejor mtodo posible; ahora bien, cmo implementarlo?

    b) Redispersin aleatoriaDe manera parecida, si disponemos de una funcin capaz de generar aleatoriamente unasecuencia de nmeros a partir de una clave, tendremos una familia de funciones deredispersin casi tan buena como la uniforme aunque, a diferencia del esquema anterior, unamisma posicin puede generarse ms de una vez en el camino de una clave.

    De nuevo tenemos el problema de definir esta funcin aleatoria. Normalmente, hemos deconformarnos con un generador de nmeros pseudoaleatorio. Otra opcin consiste enconstruir una funcin ad hoc que manipule con contundencia la clave; por ejemplo, en[AHU83, pp.134-135] se muestra una simulacin de la redispersin aleatoria mediante unamanipulacin de la clave con sumas, desplazamientos y o-exclusivos.

    c) Redispersin linealEs el mtodo de redispersin ms intuitivo y fcil de implementar, se caracteriza porque ladistancia entre hi(k) y hi+1(k) es constante para cualquier i.

    hi(k) = (h(k) + c . i) mod rSi c y r son primos entre s, se van ensayando sucesivamente todas las posiciones de latabla: h(k), h(k)+c mod r, h(k)+2c mod r, , y si hay alguna posicin vaca finalmente seencuentra; por esto, normalmente se toma c = 1 (como en el ejemplo de la fig. 4.19).

    Esta estrategia sencilla presenta, no obstante, el llamado apiamiento primario: si hay dosclaves k y k' tales que h(k) = h(k'), entonces la secuencia de posiciones generadas es lamisma para k y para k'. Observamos que, cuanto ms largas son las cadenas, ms probablees que crezcan; adems, la fusin de cadenas agrava el problema porque las hace crecer degolpe. El apiamiento primario provoca una variancia alta del nmero esperado de ensayos alacceder a la tabla.

    2 0 2 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • No obstante, la redispersin lineal presenta una propiedad interesante: la supresin noobliga a degenerar la tabla, sino que se pueden mover elementos para llenar espaciosvacos. Concretamente, al borrar el elemento que ocupa la posicin pi dentro de una cadenap1, ..., pn, i < n, se pueden mover los elementos kj que ocupan las posiciones pj, i < j n, aposiciones ps, s < j, siempre que se cumpla que s t , siendo pt = h(kj ). Es decir, al borrar unelemento se examina su cadena desde la posicin que ocupa hasta al final y se muevenhacia adelante tantos elementos como sea posible; en concreto, un elemento se muevesiempre que la posicin destino no sea anterior a su valor de dispersin (v. fig. 4.20). Estatcnica no slo evita la degeneracin espacial de la tabla sino que acerca los elementos a suvalor de dispersin y, adems, segn la distribucin de los elementos, puede partir lascadenas en dos. Ahora bien, la conveniencia del movimiento de elementos ha de serestudiada cuidadosamente porque puede acarrear los problemes ya conocidos.

    LIB i0 i1

    i0 i1 v0

    v0 i1 i2

    v0 j0 i2 LIB

    v0 i1 LIB

    v0 i1 i2 j0

    v0 j0 i2 k0

    Tabla inicial Insercin de i0 e i1

    Insercin de v0 : colisin Supresin de i0 : movimiento

    Insercin de i2 Insercin de j0 : colisin

    Supresin de i1: movimiento Insercin de k0 : colisin

    LIB LIB LIB LIB LIB LIB LIB

    LIB LIB

    LIB

    LIB LIB

    LIB

    LIB LIB

    LIB

    Fig. 4.20: id. fig. 4.19, pero con reubicacin de elementos en la supresin.

    d) Redispersin dobleLa familia de redispersin se define a partir de una segunda funcin primaria de dispersin.

    hi(k) = (h(k) + i . h'(k)) mod rSi se quiere explorar toda la tabla, es necesario que h'(k) produzca un valor entre 0 y r -1 quesea primo con r (lo ms sencillo es elegir r primo, o bien r = 2s, y que h' slo genere nmerosimpares). La ventaja respecto el mtodo anterior es que, como h' depende de la clave, esmuy improbable que h y h' colisionen a la vez; ahora bien, si esto sucede, vuelve a formarse

    Tablas 2 0 3__________________________________________________________________________________

  • una cadena de reubicacin, aunque en este caso el apiamiento no es tan grave como en elcaso lineal. Los resultados de este mtodo se acercan a la redispersin uniforme o aleatoriade manera que, a efectos prcticos, son indistinguibles.

    Tradicionalmente, la forma concreta de h' depende de la eleccin de h; as, por ejemplo, sih(k) = k mod r, puede definirse h'(k) = 1+(k mod (r-2)); idealmente, r y r -2 deberan serprimos (por ejemplo, 1021 y 1019). Otra posibilidad es tomar h' que no obligue a efectuarnuevas divisiones, como h(k) = k mod r y h'(k) = k / r (obligando que el resultado sea diferentede cero). Es importante, eso s, que h y h' sean independientes entre s para que laprobabilidad de que h(k) = h(k') y a la vez h'(k) = h'(k') sea cuadrtica respecto a r, no lineal.

    Notemos que, a diferencia del mtodo lineal, no se pueden mover elementos, porque alborrarlos no se sabe trivialmente de qu cadenas forman parte.

    e) Redispersin cuadrticaEs una variante del mtodo lineal que usa el cuadrado del ensayo:

    hi(k) = (h(k) + i2) mod rDado que la distancia entre dos valores consecutivos depende del nmero de ensayos, unacolisin secundaria no implica la formacin de cadenas siempre que h(k) h(k') ; ahora bien, sih(k) = h(k'), entonces las cadenas son idnticas.

    La rapidez de clculo puede mejorarse transformando el producto en sumas con lassiguientes relaciones de recurrencia:

    hi+1(k) = (hi(k) + di) mod r, con h0(k) = 0di+1 = di + 2, con d0 = 1

    Tal como se ha definido, no se recorren todas las posiciones de la tabla; como mximo, si r esprimo, la secuencia hi(k) puede recorrer r /2 posiciones diferentes (dado que el ensayo ies igual al ensayo r-i, mdulo r en ambos casos), por lo que la tabla puede tener sitios libresy la tcnica de reubicacin no los encuentre. En la prctica, este hecho no es demasiadoimportante, porque si despus de r /2 ensayos no se encuentra ningn sitio libre significaque la tabla est casi llena y debe regenerarse, pero en todo caso se puede solucionar elproblema obligando a r, adems de ser primo, a tomar la forma 4s + 3 y redefiniendo lafuncin como hi(k) = (h(k) + (-1)i i 2) mod r.

    f) OtrasSiempre se pueden definir otras familias ad hoc, sobre todo aprovechando la forma concretaque tiene la funcin de dispersin primaria. Por ejemplo, dada la funcin descrita en elapartado 4.3.3, podemos definir una familia de redispersin tal que hi(k) se define como laaplicacin de h sobre repr(k)+i, donde repr(k) es la representacin binaria de k (de manerasimilar al tratamiento de claves largas); esta estrategia genera todas las posiciones de la tabla.

    2 0 4 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • 4.4.3 Caracterizacin e implementacin de los mtodos de redispersin

    Como se hizo con las funciones, es necesario encapsular en universos los diferentesmtodos de redispersin que se han presentado. La caracterizacin de las funciones deredispersin ya ha sido definida en el universo CLAVE_REDISPERSIN ; el siguiente pasoconsiste en caracterizar los mtodos. Como ejemplos, estudiamos las familias lineal y doble.

    El mtodo lineal puede considerarse como un algoritmo parametrizado por una funcin dedispersin, tal como est definida en CLAVE_DISPERSIN, ms la constante multiplicadora(v. fig. 4.21). Para definir una familia de redispersin lineal basta con concretar la funcinprimaria de dispersin; en la fig. 4.22, tomamos la funcin definida en SUMA_POND_Y_DIV.

    universo REDISPERSIN_LINEAL(CLAVE_REDISPERSIN_LINEAL) esusa NATfuncin redispersin_lineal (i es nat; v es elem) devuelve nat esdevuelve (h(v) + ct*i) mod val

    funiverso

    universo CLAVE_REDISPERSIN_LINEAL caracterizausa CLAVE_DISPERSIN, NAT, BOOLops ct: natecns (ct = 0) = falso

    funiverso

    Fig. 4.21: caracterizacin de la redispersin lineal.

    universo REDISP_LINEAL_SUMA_POND_Y_DIV(E es ELEM_DISP_CONV;V1, CT son VAL_NAT) es

    instancia priv SUMA_POND_Y_DIV(F es ELEM_DISP_CONV; V2 es VAL_NAT) dondeF.elem es E.elem, F.tem es E.temF.= es E.=, F.ntems es E.tems, F.i_simo es G.i_simoF.conv es E.conv, F.base es E.base; V2.val es V1.val

    instancia REDISPERSIN_LINEAL(K es CLAVE_REDISPERSIN_LINEAL) dondeK.elem es E.elem, K.tem es E.temK.= es E.=, K.ntems es E.tems, K.i_simo es G.i_simoK.conv es E.conv, K.base es E.baseK.val es V1.val, K.h es suma_pond, K.ct es CT.val

    renombra redispersin_lineal por redisp_lin_suma_pond_y_divfuniverso

    Fig. 4.22: una instancia de la redispersin lineal.

    Tablas 2 0 5__________________________________________________________________________________

  • Por lo que respecta al mtodo doble, es necesario definir dos funciones de dispersin comoparmetros formales (v. fig. 4.23). A continuacin, se puede instanciar el universoparametrizado, por ejemplo con la funcin de suma ponderada y divisin, y separando elmdulo de ambas funciones por dos (v. fig. 4.24).

    universo REDISPERSIN_DOBLE (CLAVE_REDISPERSIN_DOBLE) esusa NATfuncin redispersin_doble (i es nat; v es elem) devuelve nat esdevuelve (h(v) + h2(v)*i) mod val

    funiverso

    universo CLAVE_REDISPERSIN_DOBLE caracterizausa CLAVE_DISPERSIN, NAT, BOOLops h2: elem natecns velem: h2(v) < val = cierto

    funiverso

    Fig. 4.23: caracterizacin de la redispersin doble.

    universo REDISP_DOBLE_SUMA_POND_Y_DIV(E es ELEM_DISP_CONV;V1, CT son VAL_NAT) es

    instancia priv SUMA_POND_Y_DIV(F es ELEM_DISP_CONV; V2 es VAL_NAT) dondeF.elem es E.elem, F.tem es E.temF.= es E.=, F.ntems es E.tems, F.i_simo es G.i_simoF.conv es E.conv, F.base es E.base; V2.val es V1.val

    renombra suma_pond por hpriminstancia priv SUMA_POND_Y_DIV(F es ELEM_DISP_CONV, V2 es VAL_NAT) donde

    F.elem es E.elem, F.tem es E.temF.= es E.=, F.ntems es E.tems, F.i_simo es G.i_simoF.conv es E.conv, F.base es E.base; V2.val es V1.val+2

    renombra suma_pond por hsecinstancia REDISPERSIN_DOBLE(K es CLAVE_REDISPERSIN_DOBLE) donde

    K.elem es E.elem, K.tem es E.temK.= es E.=, K.ntems es E.tems, K.i_simo es E.i_simoK.conv es E.conv, K.base es E.baseK.val es V1.val, K.h es hprim, K.h2 es hsec

    renombra redispersin_doble por redisp_doble_suma_pond_y_divfuniverso

    Fig. 4.24: una instancia de la redispersin doble.

    2 0 6 Estructuras de datos. Especificacin, diseo e implementacin __________________________________________________________________________________

  • 4.4.4 Variantes de las tablas de direccionamiento abierto

    Hay algunas variantes de la estrategia de direccionamiento abierto implementada en la fig.4.17, que permiten reducir el coste de las bsquedas a costa de mover elementos al insertar.As, estas variantes se pueden usar para satisfacer mejor los criterios de eficiencia de unaaplicacin dada, siempre que el coste de mover los elementos no sea elevado (es decir, quesu dimensin no sea demasiado grande y que la modificacin de los posibles apuntadores alos elementos que se mueven no sea prohibitiva dados los requerimientos de eficiencia delproblema). La implementacin de los algoritmos resultantes queda como ejercicio para ellector; se puede consultar, por ejemplo, [TeA86, pp. 531-535].

    a) Algoritmo de BrentEstrategia til cuando el nmero de consultas con xito es significativamente ms grandeque el nmero de inserciones, de manera que en estas ltimas se trabaja ms para favorecera las primeras; su comportamiento es comparativamente mejor, sobre todo a medida que latabla est ms llena. El algoritmo de Brent ha sido tradicionalmente asociado a la estrategia deredispersin doble, porque es cuando presenta los mejores resultados.

    Su funcionamiento es el siguiente: si al insertar un nuevo par se ha generado elcamino p0, , ps, definiend