edubun
Español

Tablas hash

Con 47 guardado en una tabla de 7 cubetas, la computadora va directo a la cubeta 47 mod 7 = 5 en lugar de buscar en toda la tabla.

Convertir una clave en una posición

Hasta ahora, encontrar un valor significaba buscar: hasta n pasos en un arreglo sin ordenar o en una lista enlazada. Pero los arreglos pueden llegar a cualquier posición en un paso. ¿Y si la propia clave te dijera la posición? Si tus claves fueran números enteros pequeños, digamos números de estudiante del 0 al 99, podrías tener un arreglo de 100 celdas y guardar al estudiante k en la celda k. Las notas del MIT lo llaman arreglo de acceso directo. Falla cuando las claves posibles son enormes, como números de teléfono o palabras, porque no puedes permitirte una celda para cada clave posible.

Una función hash lo resuelve comprimiendo cualquier clave en un rango pequeño de posiciones, de 0 a m − 1. Una sencilla para números enteros es h(k) = k mod m, el residuo de dividir entre m. El arreglo de m celdas es la tabla hash, y a sus celdas se les suele llamar cubetas (buckets). Para guardar 47 con m = 7, calcula 47 mod 7 = 5 y ponlo en la cubeta 5. Para encontrar 47 después, calcula lo mismo y mira solo en la cubeta 5.

Colisiones y encadenamiento

Hay un problema. Con 7 cubetas y más de 7 claves posibles, algunas claves tienen que compartir cubeta: 12, 33 y 47 dan todas residuo 5. Esto se llama colisión, y las notas del MIT señalan que no se puede evitar, por el principio del palomar. El remedio habitual es el encadenamiento: cada cubeta contiene una pequeña lista, una cadena, con todas las claves que fueron a parar ahí. Encontrar 47 cuesta entonces 1 paso por el hash más una comparación por cada clave revisada en la cadena 5. Si 12 y 33 están delante, son 1 + 3 = 4 pasos.

¿Qué largo tienen las cadenas? Si las claves se reparten de manera uniforme, cada cadena tiene unas n/m claves, un número llamado factor de carga. Mantén m más o menos tan grande como n y las cadenas siguen cortas, así que buscar, insertar y eliminar llevan en promedio un número constante de pasos. El peligro es un mal reparto. Si todas las claves van a la misma cubeta, una sola cadena contiene las n claves y la tabla no es mejor que una lista enlazada. En el laboratorio, pon 7 cubetas e inserta 14, 21 y 28: todas caen en la cubeta 0.

Cargando

Arrastra para girar. Desplaza o pellizca para acercar.

Una tabla hash de 7 cubetas que contiene 12, 25, 33, 47 y 70, con h(k) = k mod 7. Elige una clave e insértala, búscala o elimínala. El contador de pasos suma 1 por el hash más 1 por cada clave comparada en la cadena.
Las funciones hash reales mezclan todos los bits de una clave, y las bibliotecas bien diseñadas las eligen para que los datos comunes se repartan de manera uniforme. El 6.006 del MIT va más allá y elige al azar una función hash de una familia, para que ningún conjunto fijo de claves sea malo siempre. En el laboratorio se usa el simple k mod m porque se puede comprobar a mano.

Tablas hash en JavaScript

Rara vez escribirás tú mismo una tabla hash, porque JavaScript tiene Map y Set. La especificación de ECMAScript dice que "deben implementarse usando tablas hash u otros mecanismos que, en promedio, ofrezcan tiempos de acceso sublineales respecto al número de elementos". Dicho de otro modo, map.get(key) y set.has(x) deben ser más rápidos que buscar en toda la colección. MDN agrega que un Map conserva las claves en el orden de inserción y, a diferencia de un objeto normal, no contiene claves predeterminadas que puedan chocar con las tuyas.

const price = new Map();
price.set("apple", 1.2);
price.set("bread", 2.5);
console.log(price.get("bread")); // 2.5, sin recorrer la lista
console.log(price.has("milk"));  // false
const seen = new Set([4, 8, 15]);
console.log(seen.has(8));        // true
ContextoLa idea del hashing se atribuye a Hans Peter Luhn, de IBM. La historia de la búsqueda de Donald Knuth registra que en enero de 1953 Luhn escribió un memorando interno de IBM en el que proponía guardar los registros en cubetas numeradas, con encadenamiento para los que colisionaran. Sus funciones hash trabajaban con dígitos: sumar pares de dígitos vecinos y quedarse con el último dígito de cada suma, así que 31415926 se convierte en 4548. Otros ingenieros de IBM que trabajaban en la IBM 701 tuvieron la misma idea más o menos al mismo tiempo, y la primera descripción publicada fue de Arnold Dumey en 1956.
Función hashConvierte una clave en un número de cubeta de 0 a m − 1, por ejemplo k mod m.
ColisiónDos claves distintas reciben la misma cubeta. Es inevitable cuando hay más claves que cubetas.
EncadenamientoCada cubeta guarda una lista de todas las claves que fueron a parar ahí.
Factor de cargan/m, el largo promedio de las cadenas.
Map y SetLas colecciones con clave integradas en JavaScript, que deben ser más rápidas que lineales en promedio.
  1. Con h(k) = k mod 7, ¿en qué cubeta va 47?
  2. La tabla contiene 12, 25, 33, 47, 70 (insertados en ese orden) con m = 7. ¿Cuántos pasos lleva encontrar 47 (1 por el hash más las claves comparadas)?
  3. La misma tabla: ¿cuántos pasos para encontrar 12?
  4. La misma tabla: 19 no está guardado. ¿Cuántos pasos lleva buscar 19?
  5. La misma tabla: ¿cuál es el factor de carga n/m? (con dos decimales)
  6. ¿Qué es una colisión?
    • Una clave demasiado grande
    • Dos claves distintas que el hash manda a la misma cubeta
    • Eliminar una clave dos veces
    • Una tabla sin claves
  7. ¿Por qué ninguna función hash puede evitar las colisiones cuando hay más claves posibles que cubetas?
    • Las computadoras redondean los números
    • Por el principio del palomar
    • Las claves siempre son pares
    • Las cadenas son demasiado cortas
  8. Si todas las claves caen en la misma cubeta, ¿cómo se comporta la búsqueda de una clave?
    • Sigue siendo 1 paso
    • Como buscar en una lista enlazada de n claves
    • Se vuelve imposible
    • Como la búsqueda binaria
  9. ¿Qué exige la especificación de ECMAScript a los tiempos de acceso de Map?
    • Exactamente 1 paso
    • Sublineales respecto al número de elementos, en promedio
    • Θ(n log n)
    • Nada
  10. El hash de Luhn de 1953 sumaba pares de dígitos vecinos y se quedaba con el último dígito de cada suma. ¿En qué se convierte 31415926?
Ideas clave
  • Una función hash convierte una clave en un número de cubeta; k mod m es una sencilla.
  • Las colisiones son inevitables; el encadenamiento guarda una lista en cada cubeta.
  • Con un factor de carga n/m pequeño, buscar, insertar y eliminar llevan en promedio un número constante de pasos.
  • Map y Set de JavaScript deben ser más rápidos que lineales en promedio.
Fuentes de este curso (11)
  • MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
  • lecture notes 2 Data Structures (interface vs data structure, Sequence and Set interfaces, stack = insert_last/delete_last, queue = insert_last/delete_first, array, linked list, dynamic array, amortized Θ(1) append)
  • 4 Hashing (chaining, pigeonhole collisions, chain length n/m)
  • 6 Binary Trees Part 1 (height, traversal order, O(h) operations) and 7 Binary Trees Part 2: AVL (Adelson-Velsky and Landis, 1962) at ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes
  • MDN Web Docs, Array.prototype.shift (shifts all values left by 1)
  • Array.prototype.unshift, Map (insertion order, Map vs Object, spec requires sublinear average access) and Set (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
  • ECMAScript 2025 Language Specification (tc39.es/ecma262)
  • 24.1 Map Objects and 24.2 Set Objects ("must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements")
  • Array.prototype.shift steps (Repeat, while k < len: move element k to k - 1)
  • D. E. Knuth, The Art of Computer Programming vol. 3 (2nd ed. 1998) section 6.4 History (H. P. Luhn, IBM internal memorandum January 1953, hashing with chaining; digit pairs added mod 10, 31415926 -> 4548) via gwern.net copy, and IEEE Spectrum, "Hans Peter Luhn and the Birth of the Hashing Algorithm" (2018)
  • "A brief history of the stack", SIGCIS (sigcis.org/files/A brief history.pdf: Bauer and Samelson applied for the patent in 1957, US 3,047,228 granted July 31, 1962; IPL list processing by Newell, Simon and Shaw at RAND 1956) and Wikipedia, Information Processing Language (Newell, Shaw, Simon, RAND and Carnegie Institute of Technology, about 1956).

Fecha de consulta: 2026-10-18