Está en la página 1de 72

Técnicas de dispersión

Funciones de dispersión
Resolución de colisiones. Direccionamiento abierto
Resolución de colisiones. Mediante encadenamiento
Dispersión extensible. Basada en directorio
Dispersión extensible. Basada en división lineal
Implementación en Java de un contenedor que usa dispersión que resuelve las c
olisiones por direccionamiento abierto con prueba lineal
Implementación en Java de un contenedor que usa dispersión que resuelve las c
olisiones por encadenamiento con cadenas separadas
Implementación en Java de un contenedor persistente que usa dispersión exten
sible basada en directorio
Contenedores
 Muchas operaciones dependen de la localización de un
elemento.
Los mejores algoritmos de búsqueda son de O(log n).

Objetivo:
• Conocer inmediatamente dónde está o debe ir un
dato
Operaciones con coste de orden 1.
Dispersión
Conjunto de valores de claves Espacio de almacenamiento

y Función de dispersión

z
Colisión
Conjunto de valores de claves Espacio de almacenamiento

x Colisión

y
Función de dispersión
z
Técnicas de dispersión
Funciones de dispersión
Inicio
Pseudoclave
Se requiere un representación numérica de la clave.
Se puede basar en la representación interna de la clave.
En las ristras se usa valor de codificación de los caracteres
(ANSI, UNICODE, UTF-8, etc.).
En claves compuestas hay que formar la pseudoclave
haciendo que participen todas las partes.
Función de dispersión
Necesidad de adaptar la pseudoclave a la tabla.
 Método de la división o del módulo
 Método del cuadrado central
 Método plegable
 Análisis de dígitos
 Métodos dependientes de la longitud
Función de dispersión
Método de la división o del módulo.
 Es una de las funciones de dispersión más
antiguas y con mayor aceptación.
 Se define como:
 H(x) = (x mod LTabla)
 Por ejemplo, si x=35 y LTabla=11 entonces:
 H(35) = 35 mod 11 = 2
Función de dispersión
Método del cuadrado central.
 Cada valor de la pseudoclave se multiplica por sí
mismo.
 Se obtiene una dirección seleccionando un número
apropiado de bits o dígitos hacia la mitad del
cuadrado.
 Ejemplo: 123456. El cuadrado es 15241383936. Se eligen
las posiciones de la 5 a la 7; así, la dirección dada sería
138 de 15241383936.
Función de dispersión
Método plegable.
 Consiste en dividir la pseudoclave en un cierto
número de partes.
 Cada una de las cuales tiene la misma longitud que la
dirección requerida, excepto quizá la última.
 Se suman ignorando el acarreo final.
 Para el valor de pseudoclave 356942781 se divide en
tres partes —356, 942 y 781— que se suman y dan 079.
 Si los valores se toman en forma binaria, se puede
sustituir la suma por un o-exclusivo.
 Una aplicación típica permite reducir a una palabra
claves que ocupan más de una.
Función de dispersión
Análisis de dígitos.

 Consiste en seleccionar una serie de dígitos de


la pseudoclave para construir la dirección
deseada.
 Los dígitos se seleccionan en función de un
estudio previo del conjunto de claves.
 Se determina cuáles van a proporcionar
una mejor dispersión.
Función de dispersión
Análisis de dígitos.
Estudio sobre 576 DNI
Posición en la clave
Dígito 1 2 3 4 5 6 7 8

Si se desea una dirección en el 0


1
6
1
6
1
56
18
128
122
59
68
63
58
62
61
71
55

rango 0..9999 se seleccionarán 2


3
3
2
70
22
55
29
51
14
69
61
63
48
59
57
49
61
4 dígitos 4 224 102 54 26 56 62 65 55

Los que proporcionan una


5 56 88 226 23 49 63 63 67
6 0 3 4 71 55 48 60 52

distribución más uniforme son 7


8
283
1 280
4 119
14
41
49
58
55
56
54
47
51
61
54

los de las posiciones 6, 7, 8 y 5 9 0 0 1 51 46 61 51 51

por tener menor desviación Desviación


típica
100,14 82,96 65,20 37,17 6,90 5,64 5,68 6,83

típica
Función de dispersión
Métodos dependientes de la longitud.

 Si las claves tienen longitud variable, se puede


hacer intervenir su longitud en el cálculo de la
dirección.
 Una forma sería multiplicar la longitud por
una potencia de 2 —equivale a un
desplazamiento binario— y sumarla con
algunos dígitos de la pseudoclave
 Este método es combinable con los anteriores.
Función de dispersión
Clases universales de funciones de dispersión.
 Si se usan bien las funciones anteriores, dan buenos resultados,
pero no son de aplicación general.
 Sea C el conjunto de las pseudoclaves.
 Sea E={0, 1, 2, …., LTabla-1} el conjunto de posiciones en la tabla
 Sea F un conjunto de funciones de C en E.
 Tal que se pueda seleccionar aleatoriamente una función, H,
de F según la distribución uniforme.
 F es una clase universal de funciones de dispersión si la
probabilidad de que H(x)=H(y) para x≠y es como máximo
1/LTabla
Función de dispersión
Clases universales de funciones de dispersión.
Ejemplo:
 Sea p un número primo mayor o igual que cardinal de C
 Sean i y j dos enteros menores que p
 Hij(x) = ((i*x+j) mod p) mod LTabla

 F={Hij /1 <= i < p y 1 <= j <p} es una clase universal de


funciones de dispersión
 Para elegir una función basta con seleccionar dos enteros
menores que p
Técnicas de dispersión
Resolución de colisiones. Direccionamiento abierto
Inicio
Colisión
Conjunto de valores de claves Espacio de almacenamiento

x Colisión

y
Función de dispersión
z
Resolución de colisiones mediante
direccionamiento abierto
 Sitúa las colisiones en algún lugar de la tabla de dispersión
distinto del que les correspondía.
 Es necesario explorar la tabla de dispersión hasta encontrar
una posición vacía.
 La exploración debe ser eficiente y repetible.
 Debe ser repetible para poder localizar el registro.
 La secuencia de búsqueda se puede determinar mediante
la expresión:
pk(x) = (H(x) + f(k)) mod LTabla
 Donde:
 x es la clave a insertar
 pk(x) es la posición a examinar en el k-ésimo intento
 f(k) es una función que define la estrategia de búsqueda
Resolución de colisiones mediante
direccionamiento abierto
Prueba lineal: f(k) = k
pk(x) = (H(x) + k) mod LTabla
 Es decir, si a una clave le corresponde una dirección d,
se busca la primera libre en la secuencia:
d, d+1,…,Ltabla, 1, 2,…, d-1
 La prueba lineal es razonablemente buena cuando la
tabla no está demasiado llena.
 Los resultados son aceptables para un factor de carga
inferior a 0.8
 El factor de carga es la relación entre el número de datos
almacenados y el tamaño de la tabla.
Prueba lineal
Posición Clave Posiciones examinadas
 Clave H(Clave)
0 Barcelona
Valencia
Lérida 0 10, 0
9,
 Barcelona 0
1 Lérida
Valencia 0, 10,
9, 1 0, 1
 Cádiz 6
2 Madrid
Valencia 2
9, 10, 0, 1, 2
 Córdoba 6
3 Valencia 9, 10, 0, 1, 2, 3
 Granada 6
4
 Lérida 0 5
 Madrid 2 6 Cádiz
Córdoba
´Málaga
Granada
Sevilla 6
 Málaga 6 7 Córdoba
Málaga
Granada
Sevilla 6, 7
 Sevilla 6 8 Granada
Málaga
Sevilla 6, 7,
6, 7, 8
8

 Valencia 9 9 Valencia
Málaga
Sevilla 9
6, 7, 8, 9

10 Sevilla
Valencia 6, 10
9, 7, 8, 9, 10
Resolución de colisiones mediante
direccionamiento abierto
Prueba aleatoria: f(k) = k*C
pk(x) = (H(x) + k * C) mod LTabla
 Donde C es un valor mayor que 1 y sin
divisores comunes con LTabla.
 Independiza la secuencia de prueba de la
secuencia física de posiciones en la tabla.
 Ejemplo: Para LTabla = 11 y C = 3
 Secuencia de posiciones a probar desde 5:
5, 8, 0, 3, 6, 9, 1, 4, 7, 10, 2
Resolución de colisiones mediante
direccionamiento abierto
Doble dispersión: f(k) = k*H2(x)
pk(x) = (H1(x) + k * H2(x)) mod LTabla
 Usa como desplazamiento un valor dependiente de la clave: otra
función de dispersión H2(x)
 H2(x) debe ser independiente de H1(x) para que dos claves que
colisionen tengan secuencias de prueba diferentes
 Una posibilidad para LTabla primo podría ser:
H1 = x mod LTabla
H2 = (x mod (LTabla - 2)) + 1
 Por ejemplo para LTabla=11:
 Con x=75: H1(75)=9 y H2(75)=4 y la secuencia de prueba es:
9, 2, 6, 10, 3, 7, 0, 4, 8, 1, 5
 Con x=42: H1(42)=9 y H2(42)=7 y la secuencia de prueba es:
9, 5, 1, 8, 4, 0, 7, 3, 10, 6, 2
Resolución de colisiones mediante
direccionamiento abierto
Problemas
Las listas de sobrecarga acaban con posiciones con marca de libre.
Tiene que haber muchos con marca de libre.
En la secuencia de búsqueda se entremezclan claves con diferentes valores de H.
La extracción es difícil: No basta con marcar el lugar como vacío.
O bien cada posición, además de tener la marca de libre u ocupado, debe admitir la de
liberado —cuando es extraído.
 Las posiciones que se marcan como liberado, se consideran:
 Libres para las inserciones

 Ocupadas por una clave estúpida para las búsquedas

 Hace más larga de lo necesario la secuencia de colisiones en la búsqueda

O bien desplazar la secuencia de colisión desde la posición siguiente a la extraída hasta


su final.
 Se recorre la secuencia buscando un registro para tapar el hueco
 Se busca el primero que ocupe una posición que no sea la que le corresponde por H y tal que su valor

de H no esté comprendido entre la posición siguiente al hueco y la que ocupa


 Supone un alto costo por los desplazamientos

 En la dispersión de prueba no lineal la extracción sería muy compleja.


Resolución de colisiones mediante
direccionamiento abierto
 Extracción con desplazamiento de la secuencia de colisiones

Hueco
Zona libre: Si
H(Candidato) cae
en esta zona, puede
ocupar el hueco Inicio
Fin

Zona prohibida: Si
H(Candidato) cae en
esta zona, no puede
ocupar el hueco

Candidato
a ocupar Hueco
Resolución de colisiones mediante
direccionamiento abierto
 Extracción con desplazamiento de la secuencia de colisiones
Candidato

Zona prohibida: Si
H(Candidato) cae
en esta zona no Inicio
puede ocupar el
hueco
Fin

Zona libre: Si
H(Candidato) cae
en esta zona, puede
Hueco ocupar el hueco
Técnicas de dispersión
Resolución de colisiones. Mediante encadenamiento
Inicio
Encadenamiento mediante el método de las
cadenas fundidas
Se forma una lista encadenada de registros con claves
sinónimas
En lugar de reproducir en la búsqueda la secuencia de
comparaciones que se usó en la inserción.
Permite solucionar sin desplazamiento las posiciones
liberadas durante la extracción.
Se mantiene el problema de la mezcla de listas de
sinónimos diferentes.
Degenera con factores de carga altos.
Cadenas fundidas
Posición Clave Siguiente
 Clave H(Clave)
0 Valencia
Barcelona
Lérida -1 (último)
1
 Barcelona 0
1 Valencia
Lérida -1 (último)
 Cádiz 6
2 Madrid
Valencia -1 (último)
 Córdoba 6
3 Valencia -1 (último)
 Granada 6
4
 Lérida 0 5
 Madrid 2 6 Granada
Córdoba
Málaga
Sevilla
Cádiz 7
-1 (último)
 Málaga 6 7 Córdoba
Sevilla
Málaga
Granada -1 (último)
8
 Sevilla 6 8 Granada
Sevilla
Málaga -1 (último)
9

 Valencia 9 9 Sevilla
Valencia
Málaga 10
-1 (último)

10 Sevilla
Valencia -1 (último)
3
Resolución de colisiones mediante
encadenamiento de cadenas separadas
 Los registros con igual valor de función de
dispersión se encadenan en una lista.
 Las cadenas se forman en un área de
sobrecarga separada.
 La capacidad de almacenamiento está limitada
por la suma de la tabla más el área de
sobrecarga.
 Se conoce también como dispersión abierta,
frente a dispersión cerrada.
Resolución de colisiones mediante cadenas
separadas
 Clave H(Clave)
 Barcelona 0 0 Barcelona Lérida
1
 Cádiz 6
2 Madrid
 Córdoba 6
3
 Granada 6 4
 Lérida 0 5
 6 Cádiz Granada
Córdoba
Málaga
Sevilla Málaga
Granada
Córdoba
Madrid 2
7
 Málaga 6
8
 Sevilla 6 9 Valencia Granada
Córdoba Córdoba
 Valencia 9 10

Área primaria Área de sobrecarga


Resolución de colisiones mediante cadenas
separadas (versión extrema)
Lérida Barcelona

Madrid

Sevilla Málaga Granada

Valencia Cordoba Cádiz

Área primaria Área de sobrecarga


Técnicas de dispersión
Dispersión extensible. Basada en directorio
Inicio
Problema del tamaño de la tabla
 Un inconveniente: que el tamaño de la tabla tenga que
ser establecido a priori y que no se pueda variar.
 Una estimación elevada del tamaño provoca un
excesivo gasto de espacio.
 Una estimación baja sólo puede resolverse con un
costoso proceso de redispersión.
 Nuevo tamaño de tabla
 Nueva función de dispersión
 Reubicación de todos los valores de clave
 La dispersión estática se restringe a tablas cuyo
tamaño pueda ser estimado de forma fiable y que,
generalmente, se sitúan en memoria principal.
Método de dispersión extensible basado en
directorio
 Dos niveles: directorio y páginas o celdas.
 Las páginas o celdas agrupan un conjunto de
registros.
 La capacidad de la página se fija.
 El directorio discrimina, en función de los d
primeros bits de la pseudoclave, en qué página se
debe encontrar un registro.
 d se conoce como profundidad del directorio.
 Se llama profundidad de la página al número de
bits menos significativos iguales de las
pseudoclaves almacenadas en la página
Dispersión extensible basada en directorio
 La extensibilidad de la estructura se consigue:
 Desdoblando páginas
 Cuando la profundidad de la página sobrecargada es menor
que la del directorio.
 Duplicando la capacidad del directorio por incremento de su
profundidad, bit a bit
 Cuando la profundidad de la página sobrecargada coincide con
la del directorio.
 Para acceder a un dato:
 Se toman los primeros d bits de la pseudoclave para formar
una posición, DIR.
 Se toma de la entrada DIR del directorio la dirección de la
página.
 Se lee la página y se busca en ella el dato.
Dispersión extensible basada en directorio
Estado inicial:
Profundidad Profundidad
del directorio de la página
1
0
21 Se insertan valores con las pseudoclaves
01101110
000 00110011
11011011 01101110, 11011011, 11110000 y 10101010
011 11110000
10 10101010 Se inserta 00110011
00110011
11
Página sobrecargada

12
10101010
11011011 Se inserta 11100011 y 10101101
10101101
11110000
10101010 2
11100011
10101101 11011011
11110000
11100011
Página sobrecargada
Dispersión extensible basada en directorio
En general, la recuperación de un registro requiere dos
accesos externos
Uno para obtener la entrada del directorio
Otro para acceder a la página correspondiente
En algunos casos será posible mantener el directorio
completo en memoria principal.
 La recuperación sólo requiere un acceso externo.
Las páginas se pueden organizar internamente como
tablas de dispersión de tamaño fijo para eludir la
búsqueda secuencial.
Técnicas de dispersión
Dispersión extensible. Basada en división lineal
Inicio
Dispersión extensible basada en división
lineal
 La dispersión lineal consigue que la tabla pueda
incrementar su tamaño con un mínimo esfuerzo.
 Se basa en el uso de dos funciones de dispersión.
 La tabla está dividida en dos zonas.
 En cada zona se aplica una función de dispersión.
Dispersión extensible basada en división
lineal
 La dispersión lineal está pensada para ser
usada en disco.
 Se tiene la tabla que está compuesta de celdas
con cierta capacidad.
 La tabla puede crecer por el final.
 Se tiene una zona de sobrecarga donde se
mantiene una lista de celdas para absorber la
sobrecarga en las celdas de la tabla
Dispersión extensible basada en división
lineal
 Las funciones de dispersión usadas se definen
como:
 Hi(K): S  [0 .. 2i*N-1], donde N es el
número inicial de páginas debe ser potencia
de 2
 Siempre se usan dos funciones consecutivas.
 Las funciones deben cumplir que
 Hi+1(K) = Hi(K) + (2i ó 0)
Dispersión extensible basada en división
lineal
 La tabla está dividida en dos zonas por el
llamado puntero de división, pd.
 Para usar una función u otra se emplea el
siguiente criterio
 cc <- Hi(K)
 si cc < pd entonces
 cc <- Hi+1(K)
 fin si

 cc será el punto de entrada en la tabla para


buscar, insertar o extraer.
Dispersión extensible basada en división
lineal

Última celda dividida


pd i i+1
2 *N-1 2 *N-1
0 1 2

... ...

Última celda

Celdas de sobrecarga
Dispersión extensible basada en división
lineal
 El crecimiento de la tabla se produce
incrementando el puntero de división.
 A los datos en la entrada pd se accedía
mediante Hi, tras la división habrá que acceder
mediante Hi+1.
 Los datos se reparten entre las entradas pd y
pd+2i*N aplicando Hi+1
Dispersión extensible basada en división
lineal
 La tabla aumenta siempre según el puntero de
división, sin importar donde se produzcan las
inserciones.
 Los criterios para aplicar una división pueden
ser variados.
 Por ejemplo: Aplicar la división cuando se cree
una nueva celda de sobrecarga.
Ejemplo
 Se inserta 10100100, 11010101, 11001010,  Insertar 01010101 implica
00000011, 01110000, 11010001  Sobrecargar la segunda celda y
 Dividir la primera
pd pd pd pd

00000 001
01 010
10 011
11 100 101 110 111
00011000
10100100 11010101 11001010 00000011 10100100 11010101 10001110 11110111
01110000 11010001 10101110 11110111 00111100 01010101 10101110 11000111

 Insertar 11000111 implica


01011000 01010101 11000111  Sobrecargar la cuarta celda y
10001110  Dividir la segunda

 Se inserta 00111100, 10101110, 11110111

 Insertar 10001110 implica  Insertar 00011000 y 01011000 implica


 Sobrecargar la tercera celda y  Sobrecargar la primera celda y
 Dividir la tercera  Dividir la cuarta
Técnicas de dispersión
Implementación en Java de un contenedor que usa dispersión
que resuelve las colisiones por direccionamiento abierto con
prueba lineal
Inicio
Dispersion con prueba lineal
package pruebaLineal; TablaDispersión implementa la interfaz Contenedor, resuelve las
import contenedores.*; colisiones mediante direccionamiento abierto con prueba lineal
public class TablaDispersión implements Contenedor { Espacio de almacenamiento: tabla Hash
private Object elementos[];
private boolean ocupados[];
private int numElementos = 0; Número de elementos en el contenedor

// Se inicializa la tabla con capacidad por defecto para 51 elementos


public TablaDispersión() {
this(51);
}

// Se inicializa la tabla con la capacidad indicada


public TablaDispersión(int capacidad) {
elementos = new Object[capacidad]; Máximo número de elementos en el contenedor:
ocupados = new boolean[capacidad]; coincide con el tamaño de la tabla de dispersión
for(int i = 0; i < ocupados.length; i ++)
ocupados[i] = false;
}

// Función de dispersión: Hace corresponder la pseudoclave de hash del


//(hashCode) objeto con una posición del espacio de almacenamiento (elementos)
private int disp(Object e) {
return e.hashCode() % elementos.length;
}
...
Dispersion con prueba lineal
... buscar interno: devuelve la posición
private int buscar(Object e) { ... } donde se encuentra o la primera
libre, si no hay hueco devuelve -1
public void insertar(Object e) { ... }

public void extraer (Object e) { ... }

public void vaciar() { ... }


Implementación de las operaciones
public boolean está(Object e) { ... } de la interfaz Contenedor

public boolean esVacío() { ... }

public int tamaño() { ... }

}
buscar
Devuelve la posición de la clave o
private int buscar(Object e) {
int actual = disp(e);
el primer sitio libre donde situarla
int límite = actual;
// La clave se busca a partir de la posición que le corresponde y se continúa
//hasta encontrarla, hallar una posición vacía o volver al punto de partida
do {
if (!ocupados[actual]) return actual;
else
if (elementos[actual].hashCode() == e.hashCode()) return actual;
actual = (actual + 1) % elementos.length;
} while (actual != límite);
return -1; Si la posición que devuelve está
} ocupada debe contener la clave e

Si no lo encuentra ni existe espacio para ubicarla devuelve -1


insertar Inserta la clave e en el contenedor, si está no hace
nada y si la tabla está llena se produce la
excepción ArrayIndexOutOfBoundsException

public void insertar(Object e) {


int actual = buscar(e); Se localiza la posición en que está o debe estar
if (!ocupados[actual]) {
elementos[actual] = e;
ocupados[actual] = true; Si la posición está libre se inserta la clave,
numElementos++; si no hay hueco se produce la excepción
}
}
extraer
public void extraer (Object e){ Extrae la clave e del contenedor, si no está no hace nada
int actual = buscar(e);
if ((actual != -1) && (ocupados[actual])) {
ocupados[actual] = false; Si encuentra la clave, la extrae
numElementos--;
int límite = actual;
int siguiente = (actual + 1) % elementos.length;
do {
if (!ocupados[siguiente]) break;
int dondeVa = disp(elementos[siguiente]);
if (actual < siguiente) {//La zona prohibida está entre actual y siguiente
if ((dondeVa <= actual) || (dondeVa > siguiente)) {
elementos[actual] = elementos[siguiente];
ocupados[actual] = true;
ocupados[siguiente]= false; Si no se encuentra en la zona prohibida
actual = siguiente;
}
}
else {//La zona prohibida es desde actual al final y desde inicio a siguiente
if ((dondeVa <= actual) && (dondeVa > siguiente)) {
elementos[actual] = elementos[siguiente];
ocupados[actual] = true;
ocupados[siguiente]= false;
actual = siguiente;
}
dondeVa siguiente
} actual
siguiente =(siguiente + 1) % elementos.length; dondeVa
} while (siguiente != límite); actual
siguiente
} dondeVa
}
Hasta que agote la secuencia de colisión
vaciar, está, esVacío y tamaño
public void vaciar() {
for (int i = 0; i < ocupados.length; i++) Vacía el contenedor
ocupados[i] = false;
numElementos = 0;
}

public boolean está(Object e) {


int actual = buscar(e);
Determina si la clave e se
return (actual != -1)?ocupados[actual]:false; encuentra en el contenedor
}

public boolean esVacío() {


return (numElementos == 0); Determina si el contenedor está vacío
}

public int tamaño() {


return numElementos; Devuelve el número de elementos en el contenedor
}
Técnicas de dispersión
Implementación en Java de un contenedor que usa dispersión
que resuelve las colisiones por encadenamiento con cadenas
separadas
Inicio
Dispersión con cadenas separadas
package cadenasSeparadas; TablaDispersión implementa la interfaz Contenedor resuelve
import contenedores.*;
las colisiones por encadenamiento con cadenas separadas
public class TablaDispersión implements Contenedor {
private class Nodo {
Object elemento; Nodos para las cadenas de colisiones
Nodo siguiente;
public Nodo(Object e, Nodo sig) {
elemento = e;
siguiente = sig; Espacio de almacenamiento primario
} para la tabla de dispersión
}

private Nodo elementos[];


private int numElementos = 0;
Número de elementos en el contenedor

public TablaDispersión() {
this(51);
}

public TablaDispersión(int capacidad) {


elementos = new Nodo[capacidad];
}
...
Dispersión con cadenas separadas
...
// Función de dispersión: Hace corresponder la pseudoclave de hash del
//(hashCode) objeto con una posición del espacio de almacenamiento (elementos)
private int disp(Object e) {
return e.hashCode() % elementos.length;
}
// Clase auxiliar para contener información sobre la localización de un objeto en la tabla
private class ResultadoBúsqueda {
int pos;
Nodo actual, anterior;
}
/**
* Devuelve la posición de e en la tabla de dispersión, el nodo en
* que se encuentra y el nodo anterior a éste
*/
private ResultadoBúsqueda buscar(Object e) { ... }

public void insertar(Object e) { ... }


public void extraer (Object e) { ... }
public void vaciar() { ... }
public boolean está(Object e) { ... } Concreción de las operaciones del contenedor
public boolean esVacío() { ... }
public int tamaño() { ... }
}
Localiza la posición del vector
buscar en la que debe estar la clave
según su valor de dispersión,
la dirección del nodo que la
private ResultadoBúsqueda buscar(Object e) {
ResultadoBúsqueda resultado = new ResultadoBúsqueda();
contiene y la de su predecesor
resultado.pos = disp(e);
resultado.actual = elementos[resultado.pos]; La clave debe estar en la lista encadenada
resultado.anterior = null; indicado por su dirección de dispersión
while ((resultado.actual != null)
&& (!(resultado.actual.elemento.hashCode() == e.hashCode()))) {
resultado.anterior = resultado.actual;
resultado.actual = resultado.actual.siguiente;
}
return resultado;
}
insertar y extraer
Si no se encuentra la clave, la inserta
public void insertar(Object e) {
al principio de la lista del valor de
ResultadoBúsqueda resultado = buscar(e);
if (resultado.actual == null) { dispersión que le corresponda
resultado.actual = new Nodo(e, elementos[resultado.pos]);
elementos[resultado.pos] = resultado.actual;
numElementos++;
}
}

public void extraer(Object e) { Si se encuentra la clave, la


ResultadoBúsqueda resultado = buscar(e); extrae de la lista del valor de
if (resultado.actual != null) {
if (resultado.anterior == null)
dispersión que le corresponda
elementos[resultado.pos] = resultado.actual.siguiente;
else
resultado.anterior.siguiente = resultado.actual.siguiente;
numElementos--;
}
}
vaciar, está, esVacío y tamaño
public void vaciar() {
for (int i = 0; i < elementos.length; i++)
Vacía el contenedor
elementos[i] = null;
numElementos = 0;
}

public boolean está(Object e) {


ResultadoBúsqueda resultado = buscar(e);
Determina si la clave e se
return (resultado.actual != null); encuentra en el contenedor
}

public boolean esVacío() {


return (numElementos == 0); Determina si el contenedor está vacío
}

public int tamaño() {


return numElementos; Devuelve el número de elementos en el contenedor
}
Técnicas de dispersión
Implementación en Java de un contenedor persistente que usa
dispersión extensible basada en directorio
Inicio
Clase DispersiónExtensible
package dispersiónExtensible; La clase DispersiónExtensible implementa la
interfaz ContenedorPersistente mediante una
import común.*;
import contenedores.Almacenable;
dispersión extensible basada en directorio
import contenedores.ContenedorPersistente;

public class DispersiónExtensible implements ContenedorPersistente {


static final String extFicheroDirectorio = ".dir";
static final String extFicheroDatos = ".dat";
private int TDC; //Tamaño máximo de celda
private int tamañoDatos; //Tamaño en bytes de los datos almacenados

class Página { ... } Clase auxiliar para tratar las páginas

private FicheroAvanzado directorio; //Fichero con el directorio


private FicheroAvanzado páginas; //Fichero con las páginas
private int numEle; //Número de datos almacenados
private int profundidad; //Profundidad del directorio: Tamaño del directorio=2profundidad
private String camino;//Ruta de ficheros: Nombre base de los ficheros (falta extensión)

...
Clase DispersiónExtensible
...
int log2(long dato) { Calcula el logaritmo entero en base 2:
int res = 0; La posición del bit más significativo
for (int i = 1; i < 32; i++)
if (((dato >> i) & 1) == 1) res = i;
return res;
}
protected int dispersión(int profundidad, Almacenable e) {
int pse = e.hashCode();
int res = 0;
for (int i = 0; i < profundidad; i++) { Función de dispersión: Devuelve los
res <<= 1;
profundidad dígitos menos significativos
de la pseudoclave en orden inverso
if ((pse & 1) == 1) res |= 1;
pse >>= 1;
}
return res;
}
Página leerPágina(int dir) {
Página página = new Página(); Lee la página de datos de la posición dir
página.deByte(páginas.leer(dir));
if (página.dirección != dir)
throw new Fichero.ExcepcionFichero("Error al leer página");
return página;
}
...
Clase DispersiónExtensible
...
void escribirPágina(Página página) { Escribe la página en el fichero de datos
páginas.escribir(página.aByte(), página.dirección); en la posición que le corresponde
}

int buscar(Almacenable e, Página página, int posDir) {


int pos;
int posPágina;
A partir de la posición en el directorio,
posPágina = Conversor.aInt(directorio.leer(posDir));
busca el elemento y devuelve la página
página.deByte(páginas.leer(posPágina));
y la posición en la que se encuentra
for (pos = 0; pos < página.numEle; pos++)
if (e.equals(e.deByte(página.elementos[pos]))) return pos;
return -1;
}
...
Clase DispersiónExtensible
...
public DispersiónExtensible() { Constructor: Inicializa el número
numEle = 0; de elementos, la profundidad del
profundidad = 0; directorio y los ficheros lógicos
directorio = new FicheroAvanzado();
páginas = new FicheroAvanzado();
}

public void crear(String ruta, int tamañoDatos, int TDC) { ... }

public void crear(String nombre, int tamañoDatos) { Crea el directorio y el fichero de


crear(nombre, tamañoDatos, 100); datos inicializando el directorio
a dos entradas que referencian
}
Por omisión el tamaño máximo de página es 100 una misma página vacía
public void abrir(String ruta) { ... }
Abre los ficheros, calcula la profundidad
public void vaciar() { ... } del directorio y lee el número de datos
public void cerrar() { ... }
public void insertar(Object o) { ... }
Implementación del resto de operaciones
public void extraer(Object o) { ... } de la interfaz ContenedorPersistente,
public boolean está(Object o) { ... } incluidas las heredadas de Contenedor
public boolean esVacío() { ... }
public int tamaño() { ... }
}
DispersiónExtensible: Clase Página
class Página {
public int dirección; //Dirección de la página en el fichero
public int profundidad; //Profundidad de la página: bits coincidentes en la dispersión
public int numEle; //Datos ocupados en la página
public byte[][] elementos; //Vector de datos en la página

Página() {
dirección = -1; La clase Página permite el tratamiento
profundidad = 0;
numEle = 0;
de las páginas del fichero de datos
elementos = new byte[TDC][];
}

int tamaño() { //Tamaño en bytes de la página


int tam = 3 * Conversor.INTBYTES; //Cantidad base
tam += TDC * tamañoDatos; //Se añaden los datos
return tam;
}

...
DispersiónExtensible: Clase Página
...
byte[] aByte() { Devuelve un byte[] con el
int tam = tamaño();
contenido de la página actual
byte[] res = new byte[tam];
int pos = 0;
pos = Conversor.añade(res, Conversor.aByte(dirección), pos);
pos = Conversor.añade(res, Conversor.aByte(profundidad), pos);
pos = Conversor.añade(res, Conversor.aByte(numEle), pos);
for (int i = 0; i < numEle; i++)
pos = Conversor.añade(res, elementos[i], pos);
return res;
} Restaura el objeto actual a partir
de un byte[] obtenido con aByte
void deByte(byte[] datos) {
int lbi=Conversor.INTBYTES; //longitud en bytes de los enteros
dirección = Conversor.aInt(Conversor.toma(datos, 0, lbi));
profundidad = Conversor.aInt(Conversor.toma(datos, lbi, lbi));
numEle = Conversor.aInt(Conversor.toma(datos, lbi * 2, lbi));
for (int i = 0; i < numEle; i++){
int pos = lbi * 3 + i * tamañoDatos;
elementos[i] = Conversor.toma(datos, pos, tamañoDatos);
}
}
}
Operación de la interfaz ContenedorPersistente que crea el directorio y el

crear fichero de datos inicializando el directorio a dos entradas que referencian


una misma página vacía. Fija el tamaño de los datos y el de las páginas
public void crear(String ruta, int tamañoDatos, int TDC) {
this.tamañoDatos = tamañoDatos;
this.TDC = TDC; El directorio almacena enteros
Página pagina = new Página(); //Página vacía
y no tiene adjuntos
camino = ruta;
directorio.crear(camino + extFicheroDirectorio, Conversor.INTBYTES);
páginas.crear(camino + extFicheroDatos, pagina.tamaño(), 3);
pagina.dirección = páginas.tomarPágina(); El fichero páginas almacena
escribirPágina(pagina); páginas y tiene tres adjuntos
profundidad = 1;
numEle = 0; La entrada 0 del directorio
apunta a la página 0
directorio.escribir(Conversor.aByte(pagina.dirección), 0);
directorio.escribir(Conversor.aByte(pagina.dirección), 1); La entrada 1 del directorio
apunta a la página 0
páginas.adjunto(0, numEle); //Número de elementos en el contenedor 0
páginas.adjunto(1, tamañoDatos); //Número de bytes en cada dato
páginas.adjunto(2, TDC); //Número de entradas máximas por página
}
abrir, vaciar y cerrar
public void abrir(String ruta) {
camino = ruta; Operación de la interfaz
directorio.abrir(camino + extFicheroDirectorio); ContenedorPersistente que abre los
páginas.abrir(camino + extFicheroDatos); ficheros, calcula la profundidad del
numEle = páginas.adjunto(0);
tamañoDatos = páginas.adjunto(1);
directorio y lee el número de datos
TDC = páginas.adjunto(2);
profundidad = log2(directorio.tamaño());
}

public void vaciar() {


if (directorio.abierto()) { Operación heredada de la interfaz
cerrar(); Contenedor que vacía el contenedor
crear(camino, tamañoDatos, TDC);
}
}

public void cerrar() {


directorio.cerrar(); Operación de la interfaz ContenedorPersistente
páginas.cerrar();
que cierra los ficheros asociados al contenedor
}
insertar Operación heredada de la interfaz Contenedor
que inserta el objeto o si no está en el contenedor
public void insertar(Object o) {
Almacenable alma = (Almacenable) o;
byte[] e = alma.aByte();
Invoca a buscar
int posDir = dispersión(profundidad, alma);
Página página = new Página();
int pos = buscar(alma, página, posDir);
Como no está la inserta
if (pos < 0) {
if (página.numEle < TDC) {
Como cabe en la página, se actualiza
página.elementos[página.numEle++] = e;
escribirPágina(página);
numEle++;
páginas.adjunto(0, numEle);
}
...
insertar
...
Como no cabe en la página, se divide
Como la profundidad de la
página coincide con la del
else { directorio, se divide el directorio
if (página.profundidad == profundidad) {
int nentradas = (int) directorio.tamaño();
for (int i = nentradas; i < nentradas * 2; i++) Se hace crecer el directorio al doble de su tamaño,
directorio.escribir(Conversor.aByte(i), i); rellenando las posiciones [nentradas..2*nentradas-1]
for (int i = nentradas - 1; i >= 0; i--) {
byte[] dato = directorio.leer(i);
con la secuencia [nentradas..2*nentradas-1]
directorio.escribir(dato, i * 2);
directorio.escribir(dato, i * 2 + 1);
}
Se duplica el directorio copiando la dirección
profundidad++; de la posición i en las posiciones 2*i y 2*i+1
posDir = 2 * posDir;
}
Página páginaDivisión0 = new Página(); Nueva posición de la página sobrecargada
Página páginaDivisión1 = new Página();
páginaDivisión0.profundidad = página.profundidad + 1; En la misma página irán los que tengan el bit a 0
páginaDivisión0.dirección = página.dirección;
páginaDivisión1.profundidad = página.profundidad + 1;
páginaDivisión1.dirección = páginas.tomarPágina(); En una nueva página irán los que tengan el bit a 1
for (int i = 0; i < página.numEle; i++){
Almacenable dato = alma.deByte(página.elementos[i]);
int valorDispersión=dispersión(página.profundidad + 1, dato); Distribuye las claves
if ((valorDispersión & 1) == 0) entre las dos páginas
páginaDivisión0.elementos[páginaDivisión0.numEle++] = página.elementos[i];
else páginaDivisión1.elementos[páginaDivisión1.numEle++] = página.elementos[i];
}
escribirPágina(páginaDivisión0);
escribirPágina(páginaDivisión1); Número de entradas a actualizar
int difProfundidad = profundidad - páginaDivisión0.profundidad;
int nEntradasDir = 1 << difProfundidad; //=2difProfundidad
int posDirInicial = posDir - (posDir % (nEntradasDir*2)) + nEntradasDir;
int posDirFinal = posDirInicial + nEntradasDir; Primera posición a actualizar
byte[] dirByte = Conversor.aByte(páginaDivisión1.dirección);
for (int i = posDirInicial; i < posDirFinal; i++)
directorio.escribir(dirByte, i); Actualiza las direcciones del directorio a la nueva página
insertar(o);
}
} Se procede a la inserción sabiendo que existirá espacio en la página
}
extraer Operación heredada de la interfaz Contenedor
que extrae el objeto o si está en el contenedor
public void extraer(Object o) {
Almacenable alma = (Almacenable) o;
Invoca a buscar
int posDir = dispersión(profundidad, alma);
Página página = new Página();
int pos = buscar(alma, página, posDir);
if (pos >= 0) { Como está se extrae
página.numEle--;
for (int i = pos; i < página.numEle; i++)
página.elementos[i] = página.elementos[i + 1];
escribirPágina(página);
numEle--;
páginas.adjunto(0, numEle);
}
}
está, esVacío y tamaño
public boolean está(Object o) {
Almacenable alma = (Almacenable) o;
Operación heredada de la interfaz
int posDir = dispersión(profundidad, alma); Contenedor que determina si el
Página página = new Página(); objeto o se encuentra en él
return buscar(alma, página, posDir) >= 0;
}

public boolean esVacío() { Operación heredada de la interfaz Contenedor


return tamaño() == 0;
}
que determina si el contenedor está vacío

public int tamaño() {


return numEle; Operación heredada de la interfaz Contenedor que
} devuelve el número de elementos en el contenedor

También podría gustarte