El encadenamiento resuelve colisiones almacenando, en cada bucket del array, una estructura enlazada (usualmente una lista). Cuando varias claves apuntan al mismo índice, se agregan como nodos dentro de la lista de ese bucket.
Cada bucket apunta al inicio de una lista de pares clave-valor. Insertar implica agregar un nodo en esa lista; buscar y eliminar consisten en recorrerla hasta hallar la clave.
Una lista simple por bucket mantiene el orden de inserción y simplifica altas y bajas sin reubicar otros elementos.
class HashNode:
def __init__(self, clave, valor, sig=None):
self.clave = clave
self.valor = valor
self.sig = sig
class HashTable:
def __init__(self, capacidad):
self.capacidad = capacidad
self.cantidad = 0
self.buckets = [None] * capacidad
Cada entrada del array buckets almacena el puntero al primer nodo de la lista correspondiente. El campo cantidad facilita calcular el factor de carga.
def hash_cadena(clave, cap):
h = 5381
for caracter in clave:
h = ((h << 5) + h) + ord(caracter) # djb2
return h % cap
def hash_insertar(tabla, clave, valor):
idx = hash_cadena(clave, tabla.capacidad)
nodo = tabla.buckets[idx]
while nodo is not None:
if nodo.clave == clave:
nodo.valor = valor
return True
nodo = nodo.sig
nuevo = HashNode(clave, valor, tabla.buckets[idx])
tabla.buckets[idx] = nuevo
tabla.cantidad += 1
return True
Se recorre la lista del bucket buscando la clave; si ya existe se actualiza el valor, de lo contrario se agrega un nodo al inicio.
def hash_buscar(tabla, clave):
idx = hash_cadena(clave, tabla.capacidad)
nodo = tabla.buckets[idx]
while nodo is not None:
if nodo.clave == clave:
return nodo
nodo = nodo.sig
return None
La búsqueda queda acotada a la longitud de la lista dentro del bucket, en lugar de revisar toda la tabla.
def hash_eliminar(tabla, clave):
idx = hash_cadena(clave, tabla.capacidad)
actual = tabla.buckets[idx]
anterior = None
while actual is not None:
if actual.clave == clave:
if anterior is None:
tabla.buckets[idx] = actual.sig
else:
anterior.sig = actual.sig
tabla.cantidad -= 1
return True
anterior = actual
actual = actual.sig
return False
Eliminar requiere llevar un puntero previo para ajustar el enlace cuando se quita el nodo objetivo.
La clave para sostener O(1) promedio es mantener el factor de carga bajo y una función hash que disperse bien.
Copia el siguiente programa en main.py y ejecútalo. Inserta, busca y elimina claves usando encadenamiento, y muestra el estado final de la tabla.
La clave de la tabla de hash es el nombre y su valor asociado es la edad de dicha persona.
class HashNode:
def __init__(self, clave, valor, sig=None):
self.clave = clave
self.valor = valor
self.sig = sig
class HashTable:
def __init__(self, capacidad):
self.capacidad = capacidad
self.cantidad = 0
self.buckets = [None] * capacidad
def hash_cadena(clave, cap):
h = 5381
for caracter in clave:
h = ((h << 5) + h) + ord(caracter) # djb2
return h % cap
def hash_insertar(tabla, clave, valor):
idx = hash_cadena(clave, tabla.capacidad)
nodo = tabla.buckets[idx]
while nodo is not None:
if nodo.clave == clave:
nodo.valor = valor
return True
nodo = nodo.sig
nuevo = HashNode(clave, valor, tabla.buckets[idx])
tabla.buckets[idx] = nuevo
tabla.cantidad += 1
return True
def hash_buscar(tabla, clave):
idx = hash_cadena(clave, tabla.capacidad)
nodo = tabla.buckets[idx]
while nodo is not None:
if nodo.clave == clave:
return nodo
nodo = nodo.sig
return None
def hash_eliminar(tabla, clave):
idx = hash_cadena(clave, tabla.capacidad)
actual = tabla.buckets[idx]
anterior = None
while actual is not None:
if actual.clave == clave:
if anterior is None:
tabla.buckets[idx] = actual.sig
else:
anterior.sig = actual.sig
tabla.cantidad -= 1
return True
anterior = actual
actual = actual.sig
return False
def hash_imprimir(tabla):
for i in range(tabla.capacidad):
partes = []
nodo = tabla.buckets[i]
while nodo is not None:
partes.append(f"({nodo.clave} -> {nodo.valor})")
nodo = nodo.sig
print(f"[{i}]: " + " ".join(partes))
def hash_destruir(tabla):
tabla.buckets = []
tabla.cantidad = 0
tabla.capacidad = 0
if __name__ == "__main__":
tabla = HashTable(8)
hash_insertar(tabla, "juan", 50)
hash_insertar(tabla, "ana", 23)
hash_insertar(tabla, "luis", 19)
hash_insertar(tabla, "pedro", 90)
hash_insertar(tabla, "maria", 41)
print("Tabla final:")
hash_imprimir(tabla)
resultado = hash_buscar(tabla, "pedro")
print("Buscar 'pedro':", "encontrado" if resultado else "no encontrado")
hash_eliminar(tabla, "pedro")
resultado = hash_buscar(tabla, "pedro")
print("Buscar 'pedro':", "encontrado" if resultado else "no encontrado")
hash_destruir(tabla)
En VSCode, crea un proyecto de Python, reemplaza main.py con el código anterior y ejecuta. Observa cómo las colisiones se guardan en listas dentro de cada bucket.
Pulsa el botón para ver cómo se insertan juan, ana, luis, pedro y maria usando el hash djb2 sobre 8 buckets. Cada colisión se encadena al frente, igual que en el código en Python.