Cap´ıtulo 5: Busqueda en Memoria Principal
Del libro: Dise˜no de Algoritmos - Nivio Ziviani (UFMG) C y Pascal
Octubre - 2011
Ingenier´ıa de Software
´Indice
1 Conceptos b´asicos
2 B´usqueda Secuencial
3 Arboles de b´´ usqueda
´
Arboles de b´usqueda - No balanceados ´
Arboles binarios balanceados
4 Hashing
Transformaciones de clave Hashing Direccionamiento abierto
Algoritmos de b´
usqueda - TAD
• Es importante considerar los algoritmos de b´usqueda como un TAD, de tal manera que haya independencia en la implementaci´on de las operaciones. Las operaciones mas comunes son:
1 Inicializar la estructura de datos.
2 Buscar uno o mas registros con determinadas claves. 3 Insertar un nuevo registro.
4 Eliminar un registro espec´ıfico.
5 Ordenar un archivo, para tener todos los registros ordenados de acuerdo con la clave.
Diccionario
• Es el nombre utilizado comunmente para describir una estructura de datos de b´usqueda.
• Es decir, el diccionario es un TAD, que contiene b´asicamente las siguientes operaciones:
1 Inicializar 2 Buscar 3 Insertar 4 Eliminar
B´
usqueda secuencial
• Es el m´etodo de b´usqueda mas simple, la b´usqueda se realiza de manera secuencial hasta encontrar la clave requerida.
B´
usqueda Secuencial - Implementaci´
on
package Cap5; import Cap4.Item; import Cap4.MiItem;
import Cap4.PermutacionRandomica; public class Tabla {
private Item registros[]; private int n;
public Tabla (int maxN) {
this.registros = new Item[maxN+1]; this.n = 0;
}
public int Busqueda (Item reg) { this.registros[0] = reg; // centinela int i = this.n;
while (this.registros[i].comparar (reg) != 0) i--; return i;
Busqueda Secuencial - Implementaci´
on
• El m´etodos b´usqueda retorna el ´ındice del registro que contiene la clave a buscar. En caso no se encuentre retorna cero.
• Esta implementaci´on no soporta mas de un registro con la misma clave.
Busqueda Secuencial - An´
alisis
• B´usquedas con ´exito: mejor caso: C(n) = 1 peor caso: C(n) = n caso medio: C(n) = n+12
• En caso de una b´usqueda no exitosa, tendremos:
C(n) = n + 1
B´
usqueda Binaria
• La b´usqueda puede ser eficiente, si los registros se mantienen en orden.
• Para buscar un registro debemos realizar lo siguiente:
1 Comparar la clave con el registro que se encuentra en el medio de la tabla.
2 Si el registro es menor, entonces se encuentra en la primera mitad de la tabla.
3 Si la clave es mayor, entonces se encuentra en la segunda mitad de la tabla.
4 Se repite el proceso hasta que la clave sea encontrada, o quede apenas un registro cuya clave sea diferente a la requerida, lo cual significa que la b´usqueda no tuvo ´exito.
B´
usqueda binaria - Ejemplo
B´
usqueda binaria - Implementaci´
on
public int binaria (Item clave) { if (this.n == 0) return 0; int izq = 1, der = this.n, i; do {
i = (izq + der) / 2;
if (clave.comparar (this.registros[i]) > 0) izq = i + 1; else der = i - 1;
} while ((clave.comparar (this.registros[i]) != 0) && (izq <= der)); if (clave.comparar (this.registros[i]) == 0) return i;
else return 0; }
B´
usqueda binaria - An´
alisis
• En cada iteraci´on el tama˜no de la tabla es divido en dos.
• El n´umero de veces que la tabla se divide en 2 se encuentra en orden log n.
• Sin embargo, el costro de mantener una tabla ordenada es alto. Cada inserci´on en una posici´on p implica que los registros se desordenen a partir de la posici´on p.
´
Arboles de b´
usqueda
• Son estructuras de datos eficientes para almacenar informaci´on.
• Ventajas:
• Acceso directo y secuencial eficientes.
• Facilidad de inserci´on y eliminaci´on de registros.
• Buena tasa de utilizaci´on de memoria.
´
´
Arboles de b´
usqueda no balanceados - Ejemplo
• La ra´ız esta en nivel 0.
• S´ı un nodo esta en nivel i entonces la ra´ız de sus subarboles se encuentran en el nivel i + 1.
• La altura de un nodo es la longitud del camino mas largo desde dicho nodo hasta un nodo hoja.
Arbol binario - Implementaci´
on
package Cap5; import Cap4.Item;
public class ArbolBinario { private static class Nodo {
Item reg; Nodo izq, der; }
private Nodo raiz;
public ArbolBinario () { this.raiz = null; }
public Item busqueda (Item reg) { return this.busqueda (reg, this.raiz); }
public void insertar (Item reg) {
this.raiz = this.insertar (reg, this.raiz); }
public void elimina (Item reg) {
this.raiz = this.retira (reg, this.raiz); }
Arbol binario - Implementaci´
on
Operaci´on de b´usqueda:
private Item busqueda (Item reg, Nodo p) { if (p == null) return null;
else if (reg.comparar (p.reg) < 0) return busqueda (reg, p.izq); else if (reg.comparar (p.reg) > 0) return busqueda (reg, p.der); else return p.reg;
Arbol binario - Implementaci´
on
Operaci´on Insertar:
private Nodo insertar (Item reg, Nodo p) { if (p == null) {
p = new Nodo (); p.reg = reg; p.izq = null; p.der = null; }
else if (reg.comparar (p.reg) < 0) p.izq = insertar (reg, p.izq); else if (reg.comparar (p.reg) > 0) p.der = insertar (reg, p.der); else System.out.println ("Error: Registro ya existente");
return p; }
Arbol binario - Implementaci´
on
Creaci´on del ´arbol: package Cap5; import java.io.*; import Cap4.MiItem; public class CrearArbol {
public static void main (String[] args) throws Exception { ArbolBinario dicionario = new ArbolBinario ();
BufferedReader in = new BufferedReader (
new InputStreamReader (System.in)); int clave = Integer.parseInt (in.readLine());
while (clave > 0) {
MiItem item = new MiItem (clave); dicionario.insertar (item);
clave = Integer.parseInt (in.readLine()); }
} }
Arbol Binario - Implementaci´
on
Comentarios sobre la eliminaci´on:
• Si el nodo a eliminar, contiene un ´unico descendiente entonces la operaci´on es sencilla.
• En el caso de tener dos descendientes el registro a eliminar se debe sustituir por el registro que se encuentra mas a la derecha del sub-´arbol izquierdo, o por el registro que se encuentra m´as a la izquierda del sub-´arbol derecho.
Arbol binario - Implementaci´
on
Eliminaci´on de un nodo:
private Nodo antecesor (Nodo q, Nodo r) {
if (r.der != null) r.der = antecesor (q, r.der); else { q.reg = r.reg; r = r.izq; }
return r; }
private Nodo eliminar (Item reg, Nodo p) {
if (p == null) System.out.println ("Error: Registro no encontrado"); else if (reg.comparar (p.reg) < 0) p.izq = eliminar (reg, p.izq); else if (reg.comparar (p.reg) > 0) p.der = eliminar (reg, p.der); else {
if (p.der == null) p = p.izq; else if (p.izq == null) p = p.der; else p.izq = antecesor (p, p.izq); }
return p; }
Arbol binario - Implementaci´
on
Arbol binario - Implementaci´
on
Recorrido de los nodos: private void central (Nodo p) {
if (p != null) { central (p.izq); System.out.println (p.reg.toString()); central (p.der); } }
public void imprime () { this.central (this.raiz); }
Arbol binario - An´
alisis
• N´umero de comparaciones en una b´usqueda con ´exito: Mejor caso: C(n) = O(1)
Peor caso: C(n) = O(n) Caso medio: C(n) = O(log n)
• El tiempo de ejecuci´on de los algoritmos de b´usqueda en ´arboles binarios depende del formato de los ´arboles.
• Para obtener el peor caso, basta que las llaves sean insertadas en orden creciente o decreciente. En este caso el ´arbol resultante es una lista lineal, cuyo n´umero medio de comparaciones es: (n + 1)/2.
Arboles balanceados
• Si quisieramos insertar el valor 1 sobre el arbol anterior y que quede balanceado tuvieramos que realizar muchos movimientos.
• Lo que se puede hacer es tener una soluci´on intermedia de tal manera que el ´arbol quede casi balanceado.
´
Arboles SBB
• Arboles B, estructura para memoria secundaria propuesta por Bayer y´ McCreight en 1972.
• Arbol 2 − 3, es un caso especial de ´´ arbol B, donde cada nodo tiene 2 o 3 sub ´arboles.
´
Arboles SBB
• Arbol 2 − 3:´
• Las referencias a la izquierda apuntan a los nodos de un nivel mas abajo.
• Las referencias a la derecha pueden ser verticales u horizontales.
• Se elimina la asimetr´ıa en los ´arboles B binarios, y se obtienen ´arboles B Binarios Sim´etricos (SBB)s.
• Arbol SBB, Es un ´´ arbol binario con dos tipos de referencias: verticales y horizontales, tal que:
• Todos los caminos de la ra´ız hasta cada nodo externo poseen el mismo n´umero de referencias verticales.
• No puden existir dos referencias horizontales sucesivas.
´
´
Arboles SBB
• Se utilizan transformaciones para mantener el balanceamiento luego de una inserci´on o eliminaci´on.
• La clave a ser insertada (o eliminada), siempre es insertada (o eliminada) despu´es de la referencia vertical mas baja en el ´arbol.
• Dependiendo de la situaci´on anterior, pueden aparecer dos referencias horizontales sucesivas.
´
´
Arboles SBB - Ejemplo
´
Arboles SBB - Estructura del diccionario
package Cap5; import Cap4.Item; public class ArbolSBB {
private static class Nodo { Item reg;
Nodo izq, der; byte indI, indD; }
private static final byte Horizontal = 0; private static final byte Vertical = 1; private Nodo raiz;
private boolean propSBB; public ArbolSBB () {
this.raiz = null; this.propSBB = true; }
´
Arboles SBB - M´
etodos para mantener la propiedad de
SBB
private Nodo ii (Nodo ap) {
Nodo ap1 = ap.izq; ap.izq = ap1.der; ap1.der = ap; ap1.indI = Vertical; ap.indI = Vertical; ap = ap1; return ap;
}
private Nodo id (Nodo ap) {
Nodo ap1 = ap.izq; Nodo ap2 = ap1.der; ap1.indD = Vertical; ap.indI = Vertical; ap1.der = ap2.izq; ap2.izq = ap1; ap.izq = ap2.der; ap2.der = ap; ap = ap2;
return ap; }
private Nodo dd (Nodo ap) {
Nodo ap1 = ap.der; ap.der = ap1.izq; ap1.izq = ap; ap1.indD = Vertical; ap.indD = Vertical; ap = ap1; return ap;
}
private Nodo di (Nodo ap) {
Nodo ap1 = ap.der; Nodo ap2 = ap1.izq; ap1.indI = Vertical; ap.indD = Vertical; ap1.izq = ap2.der; ap2.der = ap1; ap.der = ap2.izq; ap2.izq = ap; ap = ap2;
return ap; }
´
Arboles SBB - M´
etodo Insertar
private Nodo insertar (Item reg, Nodo padre, Nodo hijo, boolean hijoIzq) { if (hijo == null) {
hijo = new Nodo (); hijo.reg = reg; hijo.indI = Vertical; hijo.indD = Vertical; hijo.izq = null; hijo.der = null; if (padre != null)
if (hijoIzq) padre.indI = Horizontal; else padre.indD = Horizontal; this.propSBB = false;
}
else if (reg.comparar (hijo.reg) < 0) {
hijo.izq = insertar (reg, hijo, hijo.izq, true); if (!this.propSBB)
if (hijo.indI == Horizontal) { if (hijo.izq.indI == Horizontal) {
hijo = this.ii (hijo); // transformaci´on izquierda-izquierda if (padre != null)
if (hijoIzq) padre.indI=Horizontal; else padre.indD=Horizontal; }
else if (hijo.izq.indD == Horizontal) {
hijo = this.id (hijo); // transformaci´on izquierda-derecha if (padre != null)
if (hijoIzq) padre.indI=Horizontal; else padre.indD=Horizontal; }
}
else this.propSBB = true; }
´
Arboles SBB - M´
etodo Insertar
else if (reg.comparar (hijo.reg) > 0) {
hijo.der = insertar (reg, hijo, hijo.der, false); if (!this.propSBB)
if (hijo.indD == Horizontal) { if (hijo.der.indD == Horizontal) {
hijo = this.dd (hijo); // transformacion derecha-derecha if (padre != null)
if (hijoIzq) padre.indI=Horizontal; else padre.indD=Horizontal; }
else if (hijo.der.indI == Horizontal) {
hijo = this.di (hijo); // transformaci´on derecha-izquierda if (padre != null)
if (hijoIzq) padre.indI=Horizontal; else padre.indD=Horizontal; }
}
else this.propSBB = true; }
else {
System.out.println ("Error: Registro ya existente"); this.propSBB = true;
}
return hijo; }
Procedimiento Eliminar
El procedimiento eliminar, presenta 3 m´etodos auxiliares:
• cortaIzq (cortaDer), es llamado cuando un nodo hoja (que es
referenciado por un apuntador vertical), es retirado del subarbol de la izquierda (derecha), diminuyendo la altura luego de la eliminaci´on.
• Cuando el nodo a ser retirado tiene dos descendientes, el m´etodo antecesor localiza el nodo antecesor a ser intercambiarlo con el nodo que se va a eliminar.
´
Arboles SBB - M´
etodo cortaIzq para eliminar nodo
private Nodo cortaIzq (Nodo ap) { if (ap.indI == Horizontal) {
ap.indI = Vertical; this.propSBB = true; }
else if (ap.indD == Horizontal) {
Nodo ap1 = ap.der; ap.der = ap1.izq; ap1.izq = ap; ap = ap1; if (ap.izq.der.indI == Horizontal) {
ap.izq = this.di (ap.izq); ap.indI = Horizontal; }
else if (ap.izq.der.indD == Horizontal) { ap.izq = this.dd (ap.izq); ap.indI = Horizontal; } this.propSBB = true; } else { ap.indD = Horizontal; if (ap.der.indI == Horizontal) { ap = this.di (ap); this.propSBB = true; }
else if (ap.der.indD == Horizontal) { ap = this.dd (ap); this.propSBB = true; }
} return ap; }
´
Arboles SBB - M´
etodo cortaDer para eliminar nodo
private Nodo cortaDer (Nodo ap) { if (ap.indD == Horizontal) {
ap.indD = Vertical; this.propSBB = true; }
else if (ap.indI == Horizontal) {
Nodo ap1 = ap.izq; ap.izq = ap1.der; ap1.der = ap; ap = ap1; if (ap.der.izq.indD == Horizontal) {
ap.der = this.id (ap.der); ap.indD = Horizontal; }
else if (ap.der.izq.indI == Horizontal) { ap.der = this.ii (ap.der); ap.indD = Horizontal; } this.propSBB = true; } else { ap.indI = Horizontal; if (ap.izq.indD == Horizontal) { ap = this.id (ap); this.propSBB = true; }
else if (ap.izq.indI == Horizontal) { ap = this.ii (ap); this.propSBB = true; }
} return ap; }
´
Arboles SBB - M´
etodo antecesor para eliminar nodo
private Nodo antecesor (Nodo q, Nodo r) { if (r.der != null) {
r.der = antecesor (q, r.der);
if (!this.propSBB) r = this.cortaDer (r); }
else { q.reg = r.reg; r = r.izq;
if (r != null) this.propSBB = true; }
return r; }
´
Arboles SBB - M´
etodo eliminar para retirar un nodo
private Nodo eliminar (Item reg, Nodo ap) { if (ap == null) {
System.out.println ("Error: Registro no encontrado"); this.propSBB = true;
}
else if (reg.comparar (ap.reg) < 0) { ap.izq = eliminar (reg, ap.izq);
if (!this.propSBB) ap = this.cortaIzq (ap); //Cuando el nodo es hoja }
else if (reg.comparar (ap.reg) > 0) { ap.der = eliminar (reg, ap.der);
if (!this.propSBB) ap = this.cortaDer (ap); //Cuando el nodo es hoja }
else { // registro encontrado this.propSBB = false; if (ap.der == null) {
ap = ap.izq;
if (ap != null) this.propSBB = true; }
else if (ap.izq == null) { ap = ap.der;
if (ap != null) this.propSBB = true; }
else {
ap.izq = antecesor (ap, ap.izq);
if (!this.propSBB) ap = this.cortaIzq (ap); }
} return ap; }
Hashing
• Es un m´etodo de b´usqueda con transformaci´on de clave que esta conformado por dos etapas principales:
1 Calcular el valor de la funci´on de transformaci´on o funci´on hashing, la cual transforma la clave de b´usqueda en una direcci´o o entrada a la tabla.
2 Como dos o mas claves pueden transformarse en la misma direcci´on, es necesario disponer de una t´ecnica para manejar las colisiones.
Fuciones de transformaci´
on
• Una funci´on de transformaci´on debe mapear claves en enteros dentro de un intervalo [0, . . . , M ], donde M es el tama˜no de la tabla.
• La funci´on de transformaci´on ideal es aquella que:
1 Es simple de calcular.
2 Para cada clave de entrada, cada una de las salidas es igualmente problable de ocurrir.
• C´omo las transformaciones sobre las claves son aritm´eticas, se deben transformar las claves no num´ericas en n´umeros.
• En Java basta con convertir cada caracter de la clave no num´erica en un entero.
Fuciones de transformaci´
on - M´
etodo mas usado
• Es el resto de la divisi´on por M :
h(K) = KmodM
donde K es el n´umero entero correspondiente a la clave.
• Se debe tener cuidado con la elecci´on del valor de M . M debe ser un n´umero primo, mas no cualquier n´umero primo: deben ser evitados los n´umeros primos obtenidos a partir de:
bi± j
• Donde b es la base de un conjunto de caracteres (generalmente b = 64 para BCD, 128 para ASCII, 256 para EBCDIC, y 100 para algunos c´odigos decimales, e i y j son enteros peque˜nos.
Transformaci´
on de claves no num´
ericas
• Las claves no num´ericas deben ser transformadas en n´umeros:
K = n−1 X i=0 clave[i] × p[i] .
• Donde, n es el n´umero de caracteres de la clave.
• clave[i] corresponde a la representaci´on ASCII o UNICODE del i-´esimo caracter de la clave.
• p[i] es un entero de un conjunto de pesos generados de manera aleatoria para 0 ≤ i ≤ n − 1.
• Una de las ventajas de usar pesos, es que dos conjuntos diferentes de pesos p1[i] y p2[i], 0 ≤ i ≤ n − 1, llevan a dos funciones de
Transformaci´
on de claves no num´
ericas
• El programa genera un peso para cada caracter de una cadena de n
caracteres.
private int[] generaPesos (int n) { int p[] = new int[n];
java.util.Random rand = new java.util.Random (); for (int i = 0; i < n; i++) p[i] = rand.nextInt(M) + 1; return p;
}
• Implementaci´on de funci´on de transformaci´on:
private int h (String clave, int[] pesos) { int suma = 0;
for (int i = 0; i < clave.length(); i++)
suma = suma + ((int)clave.charAt (i)) * pesos[i]; return suma % this.M;
Listas encadenadas
• Una de las formas de resolver la colisi´on es utilizar una lista lineal encadenada (con apuntadores).
• Ejemplo: sea la i-´esima letra del alfabeto y es representada por el numero i y una funci´on de transformaci´on h(clave) = clave m´od M es utilizada para M = 7, el resultado de la inserci´on de las claves P ESQU ISA en la tabla es la siguiente:
• h(A) = h(1) = 1, h(E) = h(5) = 5, h(S) = h(19) = 5 y as´ı en
Estructura de operaciones del diccionario
• En cada entrada de la lista debe ser almacenados una clave y un registro de datos cuyos datos depende de la aplicaci´on.
• La clase interna celda es utilizada para representar una entrada en una lista de claves que son mapeadas a una direcci´on i de la tabla, siendo 0 ≤ i ≤ M − 1
• El m´etodo equals de la clase celda es usado para verificar si dos celdas son iguales (si poseen la misma clave).
• La operaci´on inicializa es implementada por el constructor de la clase T ablaHash
Estructura de operaciones del diccionario
package Cap5;
import Cap3.apuntadores.Lista; public class TablaHash {
private static class Celda { String clave;
Object item;
public Celda (String clave, Object item) { this.clave = clave; this.item = item; }
public boolean equals (Object obj) { Celda cel = (Celda)obj;
return clave.equals (cel.clave); }
public String toString () { return " " + item.toString (); }
}
private int M; private Lista tabla[]; private int pesos[];
public TablaHash (int m, int maxTamClave) { this.M = m; this.tabla = new Lista[this.M];
for (int i = 0; i < this.M; i++) this.tabla[i] = new Lista (); this.pesos = this.generaPesos (maxTamClave);
Estructura de las operaciones de un diccionario
public Object busqueda (String clave) { int i = this.h (clave, this.pesos); if (this.tabla[i].vacia()) return null; else {
Celda cel=(Celda)this.tabla[i].busqueda(new Celda(clave,null)); if (cel == null) return null;
else return cel.item; }
}
public void insertar (String clave, Object item) { if (this.busqueda (clave) == null) {
int i = this.h (clave, this.pesos);
this.tabla[i].insertar (new Celda (clave, item)); }
else System.out.println ("Registro ya se encuentra"); }
public void eliminar (String clave) throws Exception { int i = this.h (clave, this.pesos);
Celda cel = (Celda)this.tabla[i].retirar (new Celda (clave,null)); if (cel == null) System.out.println ("Registro a eliminar no encontrado"); }
An´
alisis
• Asumiendo que cualquier item del conjunto tiene igual probabilidad de ser direccionado para cualquier entrada de la tabla, entonces la longitud esperada de cada lista es de N/M , donde N representa el n´umero de registros en la tabla y M el tama˜no de la tabla.
• Luego, las operaciones de b´usqueda, inserci´on y eliminaci´on tienen un costro de O(1 + N/M ) operaciones en promedio. La constante 1 representa el tiempo para encontrar la entrada de la tabla, y N/M el tiempo para prerecorrer la lista. Para valores de M pr´oximos a N , el tiempo se vuelve constante, esto es independiente de N .
Direccionamiento abierto
• Cuando el n´umero de registros a ser almacenados en la tabla puede ser previamente estimado, entonces no habr´a necesidad de usar listas enlazadas para almacenar los registros.
• Existen diferentes m´etodos para almacenar N registros en una tabla de tama˜no M > N , los cuales utilizan las entradas vacias para resolver las colisiones.
• En el direccionamiento abierto todas las claves son almacenadas en la propia tabla, sin la necesidad de utilizar listas enlazadas.
• Existen diferentes propuestas para la elecci´on de las ubicaciones alternativas. La mas simple es el hashing lineal, donde la posici´on hj en la tabla esta dada por:
Direccionamiento abierto - Ejemplo
• Se la i-´esima letra del alfabeto es represenada por el n´umero i y una funci´on de transformaci´on h(clave) = clave m´od M es utilizada para M = 7.
• Para la palabra LU N ES en la tabla, utilizando el hashing lineal se muestra a continuaci´on.
• h(L) = h(12) = 5, h(U ) = h(21) = 0, h(N ) = h(14) = 0,
Direccionamiento abierto - Estructura de operaciones
• La tabla esta constitu´ıda por un vector de celdas.
• La clase interna Celda es utilizada para representar una celda de la tabla.
• La operaci´on de inicializaci´on se encuentra implementada por el constructor de la clase T ablaHash.
• Los m´etodos utilizan algunas operaciones auxiliares durante la ejecuci´on.
Direccionamiento abierto - Estructura de operaciones
package Cap5.Direccionamiento; public class TablaHash {
private static class Celda {
String clave; Object item; boolean eliminado; public Celda (String clave, Object item) {
this.clave = clave; this.item = item; this.eliminado = false;
}
public boolean equals (Object obj) { Celda cel = (Celda)obj;
return clave.equals (cel.clave); }
public String toString () { return " " + item.toString (); }
}
private int M; // tama~no de la tabla private Celda tabla[];
private int pesos[];
public TablaHash (int m, int maxTamChave) { this.M = m; this.tabla = new Celda[this.M];
for (int i = 0; i < this.M; i++) this.tabla[i] = null; this.pesos = this.generaPesos (maxTamChave); }
Direccionamiento abierto - Estructura de operaciones
public Object busqueda (String clave) { int indice = this.busquedaIndice (clave);
if (indice < this.M) return this.tabla[indice].item; else return null;
}
public void insertar (String clave, Object item) { if (this.busqueda (clave) == null) {
int inicial = this.h (clave, this.pesos); int indice = inicial; int i = 0; while (this.tabla[indice] != null &&
!this.tabla[indice].eliminado &&
i < this.M) indice = (inicial + (++i)) % this.M; if (i < this.M) this.tabla[indice] = new Celda (clave, item); else System.out.println ("Tabla llena");
} else System.out.println ("Registro ya existente"); }
Direccionamiento abierto - Estructura de operaciones
public void eliminar (String clave) throws Exception { int i = this.busquedaIndice (clave);
if (i < this.M) {
this.tabla[i].eliminado = true; this.tabla[i].clave = null; } else System.out.println ("Registro no esta encontrado"); }
private int busquedaIndice (String clave) { int inicial = this.h (clave, this.pesos); int indice = inicial; int i = 0; while (this.tabla[indice] != null &&
!clave.equals (this.tabla[indice].clave) && i < this.M) indice = (inicial + (++i)) % this.M; if (this.tabla[indice] != null &&
clave.equals (this.tabla[indice].clave)) return indice; else return this.M; // b´usqueda sin ´exito
An´
alisis
• Sea α = N/M el factor de carga de la tabla, seg´un Knuth(1973), el costo de una b´usqueda con ´exito es:
C(n) = 1
2(1 + 1 1 − α)
• El hashing lineal sufre de un mal llamado agrupamiento (clustering)
• Este fen´omeno ocurre a medida que la tabla se comienza a llenar, pues una inserci´on de una nueva clave tiende a ocupar una posici´on en la tabla que esta contigua a otras posiciones ya ocupadas, lo que impacta sobre el tiempo de b´usqueda.
• A pesar de que el hashing lineal es un m´etodo relativamente pobre para resolver las colisiones, los resultados presentados son buenos.
Ventajas y desventajas
• Ventajas:
• Alta eficiencia del costo de la busqueda O(1) para el caso medio.
• F´acil implementaci´on.
• Desventajas:
• Tiene un costo alto para recuperar los registros en orden lexicogr´afico de las llaves, es necesario ordenar el archivo.
Hashing Perfecto
• Si h(xi) = h(xj) si y solamente s´ı i = j, entonces no hay colisiones en la funci´on de transformaci´on. Es llamada funci´on de
transformaci´on perfecta o funci´on de hashing perfecta (hp).
• Si el n´umero de claves N y el tama˜no de la tabla M son iguales (α = N/M = 1), entonces tenemos una funci´on de transformaci´on perfecta m´ınima.
• Si xi ≤ xj y hp(xi) ≤ hp(xj), entonces el orden lexicografico es mantenido. Tenemos una funci´on de transformaci´on perfecta m´ınima preservando el orden.