Imagina una biblioteca donde no tienes que buscar el libro por orden alfabético. Simplemente tienes una fórmula matemática que te dice exactamente en qué estante y posición está. Eso es el Hashing: transformar una clave de búsqueda en una dirección de memoria directa para obtener acceso en tiempo constante O(1).

Imagen 43. Funcionamiento conceptual de una Tabla Hash
Diagrama de flujo básico: Muestra entradas (strings/nombres) pasando por una "Caja Negra" (Función Hash) que escupe números (índices) correspondientes a casillas en un arreglo.
Una función hash debe ser determinista: para la misma entrada, siempre debe dar el mismo índice. Además, debe ser rápida de calcular y distribuir los datos uniformemente. La operación matemática estrella aquí es el Módulo (%).

Imagen 44. Uso del operador Módulo para índices
Visualización del ajuste: Si el hash crudo es 1,000,500 pero mi array es de tamaño 10, la operación 1,000,500 % 10 ubica el dato en la posición 0. El módulo garantiza que nunca nos salgamos del array.
Es matemáticamente imposible evitar que dos llaves diferentes generen el mismo índice (ej: "roma" y "amor" podrían sumar lo mismo). Esto se llama colisión. Para resolverlo, la técnica más común y robusta es el "Encadenamiento" (Chaining).

Imagen 45. Resolución de colisiones mediante Listas Enlazadas
Diagrama de Encadenamiento: Muestra el Array principal, pero en lugar de guardar el dato directamente, cada casilla guarda un puntero a una Lista Enlazada donde se acumulan las colisiones.
Para implementar una Tabla Hash con encadenamiento en C, necesitamos un Array de Punteros. Cada puntero será la "cabeza" de una lista enlazada. Inicialmente, todos apuntan a NULL.

Imagen 46. Estructura en memoria: Array de Punteros
Arquitectura en RAM: Visualización del "Array de cabezas". Muestra cómo la tabla principal reside en memoria estática o dinámica, apuntando a nodos dispersos en el Heap.