đ ImplĂ©mentation dâune Table de Hachage (21-hash-table.c) đ
Une table de hachage est une structure de données essentielle pour la recherche rapide. Elle est couramment utilisée pour les entretiens FAANG et les projets comme push_swap
. Nous allons détailler étape par étape une implémentation en C. Cette table utilisera chaining (chaßnes de listes chaßnées) pour gérer les collisions.
Plan de Travail
- Comprendre la structure de la table de hachage.
- Définir les types de données :
- Clé (
key
) : un entier ou une chaĂźne de caractĂšres. - Valeur (
value
) : un entier ou une structure.
- Clé (
- Créer une fonction de hachage.
- Gérer les collisions avec des listes chaßnées.
- Implémenter les principales opérations :
- Insertion (
insert
). - Recherche (
search
). - Suppression (
delete
).
- Insertion (
- Tester lâimplĂ©mentation.
Code Complet : Table de Hachage
Voici une implémentation étape par étape :
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define TABLE_SIZE 10
typedef struct s_node {
char *key;
int value;
struct s_node *next;
} t_node;
typedef struct s_hash_table {
t_node *buckets[TABLE_SIZE];
} t_hash_table;
unsigned int hash(const char *key)
{
unsigned int hash;
const char *current;
hash = 0;
current = key;
while (*current)
{
hash = (hash * 31) + *current;
current++;
}
return (hash % TABLE_SIZE);
}
t_hash_table *create_table(void)
{
t_hash_table *table;
int i;
table = malloc(sizeof(t_hash_table));
if (table == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
return (NULL);
}
i = 0;
while (i < TABLE_SIZE)
{
table->buckets[i] = NULL;
i++;
}
return (table);
}
void insert(t_hash_table *table, const char *key, int value)
{
unsigned int index;
t_node *new_node;
index = hash(key);
new_node = malloc(sizeof(t_node));
if (new_node == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
return;
}
new_node->key = strdup(key);
if (new_node->key == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
free(new_node);
return;
}
new_node->value = value;
new_node->next = table->buckets[index];
table->buckets[index] = new_node;
}
int search(t_hash_table *table, const char *key)
{
unsigned int index;
t_node *current;
index = hash(key);
current = table->buckets[index];
while (current != NULL)
{
if (strcmp(current->key, key) == 0)
return (current->value);
current = current->next;
}
return (-1);
}
void delete(t_hash_table *table, const char *key)
{
unsigned int index;
t_node *current;
t_node *previous;
index = hash(key);
current = table->buckets[index];
previous = NULL;
while (current != NULL)
{
if (strcmp(current->key, key) == 0)
{
if (previous == NULL)
table->buckets[index] = current->next;
else
previous->next = current->next;
free(current->key);
free(current);
return;
}
previous = current;
current = current->next;
}
}
void display(t_hash_table *table)
{
int i;
t_node *current;
i = 0;
while (i < TABLE_SIZE)
{
printf("Bucket %d: ", i);
current = table->buckets[i];
while (current != NULL)
{
printf("[%s: %d] -> ", current->key, current->value);
current = current->next;
}
printf("NULL\n");
i++;
}
}
void free_table(t_hash_table *table)
{
int i;
t_node *current;
t_node *temp;
i = 0;
while (i < TABLE_SIZE)
{
current = table->buckets[i];
while (current != NULL)
{
temp = current;
current = current->next;
free(temp->key);
free(temp);
}
i++;
}
free(table);
}
int main(void)
{
t_hash_table *table;
int result;
table = create_table();
if (table == NULL)
return (EXIT_FAILURE);
insert(table, "Alice", 25);
insert(table, "Bob", 30);
insert(table, "Charlie", 35);
display(table);
result = search(table, "Bob");
printf("Search for Bob: %d\n", result);
result = search(table, "Eve");
printf("Search for Eve: %d\n", result);
delete(table, "Alice");
display(table);
free_table(table);
return (EXIT_SUCCESS);
}
Explications
1. Structure de la Table
- Buckets : Chaque entrée de la table contient une liste chaßnée pour gérer les collisions.
- Node : Contient la clé, la valeur et un pointeur vers le prochain élément.
2. Fonction de Hachage
- Utilise une combinaison simple (multiplication et addition) pour convertir une chaĂźne en un indice.
3. Gestion des Collisions
- Les collisions sont rĂ©solues par des listes chaĂźnĂ©es oĂč plusieurs Ă©lĂ©ments peuvent partager le mĂȘme index.
4. Opérations
- Insertion : Ajoute un élément au début de la liste chaßnée.
- Recherche : Parcourt la liste pour trouver une clé correspondante.
- Suppression : Modifie les pointeurs pour exclure un nĆud spĂ©cifique.
5. Tests
- Le programme principal teste lâinsertion, la recherche, et la suppression, tout en affichant lâĂ©tat de la table.
Prochaines Ătapes
- Optimiser la fonction de hachage pour de meilleurs résultats avec des chaßnes longues.
- Ajouter des tests unitaires pour valider chaque fonctionnalité.
- Implémenter une méthode pour gérer des tailles dynamiques (redimensionnement de la table).
đ Bonnes pratiques : Utilisez cette base pour implĂ©menter des solutions adaptĂ©es aux besoins FAANG en mettant lâaccent sur les performances et la gestion de la mĂ©moire ! đŻ
đ Guide Complet pour Comprendre les Tables de Hachage (Hash Tables)
Les tables de hachage sont lâune des structures de donnĂ©es les plus importantes et couramment utilisĂ©es en informatique. Voici un guide complet pour comprendre leur fonctionnement, Ă©tape par Ă©tape.
1. Quâest-ce quâune Table de Hachage ?
Une table de hachage est une structure de données qui stocke des paires clé-valeur.
Elle permet de récupérer rapidement une valeur à partir de sa clé, souvent en temps constant, grùce à une fonction de hachage.
Pourquoi utiliser une table de hachage ?
- Rapidité : Les recherches, insertions et suppressions sont rapides (environ O(1) en moyenne).
- SimplicitĂ© : AccĂ©der Ă une valeur Ă partir dâune clĂ© est intuitif.
- Utilisations courantes :
- RĂ©pertoires tĂ©lĂ©phoniques (nom â numĂ©ro).
- Indexation des bases de données.
- SystĂšmes de cache.
2. Concepts Clés
2.1 Les Clés et les Valeurs
- ClĂ© : Une donnĂ©e unique (ex. : âNomâ, âIdentifiantâ).
- Valeur : La donnĂ©e associĂ©e Ă cette clĂ© (ex. : âĂgeâ, âScoreâ).
Clé | Valeur |
---|---|
âAliceâ | 25 |
âBobâ | 30 |
âCharlieâ | 35 |
2.2 La Fonction de Hachage
Une fonction de hachage transforme une clé en un indice dans un tableau.
Exemple :
Si on a un tableau de taille 10 et une clé "Alice"
, la fonction de hachage calcule un indice entre 0 et 9.
Fonction de Hachage Exemple :
unsigned int hash(const char *key) {
unsigned int hash = 0;
while (*key) {
hash = (hash * 31) + *key++; // Multiplier par 31 et ajouter le code ASCII
}
return hash % TABLE_SIZE; // Réduire dans la plage [0, TABLE_SIZE-1]
}
2.3 Les Collisions
Deux clĂ©s peuvent produire le mĂȘme indice (collision).
Exemple :
- Clé
"Alice"
donne lâindice 2. - ClĂ©
"Eve"
donne aussi lâindice 2.
Pour résoudre ce problÚme, on utilise :
- Chaining : Une liste chaßnée par indice.
- Open Addressing : Chercher une autre position libre dans le tableau.
3. Comment Fonctionne une Table de Hachage ?
-
Insertion
- Convertir la clé en un indice avec la fonction de hachage.
- Placer la valeur dans la case correspondante ou gérer les collisions.
-
Recherche
- Hacher la clĂ© pour obtenir lâindice.
- Parcourir la liste chaßnée (ou trouver la case directement).
-
Suppression
- Retirer un Ă©lĂ©ment dans la liste chaĂźnĂ©e ou marquer la case comme âsupprimĂ©eâ.
4. Exemple Visuel
Indice | Liste Chaßnée |
---|---|
0 | NULL |
1 | [âEveâ â 40] â NULL |
2 | [âAliceâ â 25] â [âBobâ â 30] â NULL |
3 | NULL |
⊠| ⊠|
5. Avantages et Inconvénients
Avantages
- TrÚs rapide pour rechercher, insérer et supprimer des éléments.
- Facile à implémenter pour de nombreuses applications.
Inconvénients
- Les collisions peuvent réduire les performances.
- La fonction de hachage doit ĂȘtre bien conçue.
- La gestion dynamique (agrandissement) peut ĂȘtre complexe.
6. Exemple Complet en C
Voici un programme complet pour insérer, rechercher et supprimer des éléments dans une table de hachage :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TABLE_SIZE 10
typedef struct Node {
char *key;
int value;
struct Node *next;
} Node;
typedef struct HashTable {
Node *buckets[TABLE_SIZE];
} HashTable;
// Fonction de hachage
unsigned int hash(const char *key) {
unsigned int hash = 0;
while (*key) {
hash = (hash * 31) + *key++;
}
return hash % TABLE_SIZE;
}
// Créer une table de hachage
HashTable *create_table() {
HashTable *table = malloc(sizeof(HashTable));
if (!table) exit(EXIT_FAILURE);
for (int i = 0; i < TABLE_SIZE; i++) table->buckets[i] = NULL;
return table;
}
// Insertion
void insert(HashTable *table, const char *key, int value) {
unsigned int index = hash(key);
Node *new_node = malloc(sizeof(Node));
new_node->key = strdup(key);
new_node->value = value;
new_node->next = table->buckets[index];
table->buckets[index] = new_node;
}
// Recherche
int search(HashTable *table, const char *key) {
unsigned int index = hash(key);
Node *current = table->buckets[index];
while (current) {
if (strcmp(current->key, key) == 0) return current->value;
current = current->next;
}
return -1; // Non trouvé
}
// Suppression
void delete(HashTable *table, const char *key) {
unsigned int index = hash(key);
Node *current = table->buckets[index];
Node *prev = NULL;
while (current) {
if (strcmp(current->key, key) == 0) {
if (prev) prev->next = current->next;
else table->buckets[index] = current->next;
free(current->key);
free(current);
return;
}
prev = current;
current = current->next;
}
}
// Affichage
void display(HashTable *table) {
for (int i = 0; i < TABLE_SIZE; i++) {
printf("Bucket %d: ", i);
Node *current = table->buckets[i];
while (current) {
printf("[%s: %d] -> ", current->key, current->value);
current = current->next;
}
printf("NULL\n");
}
}
// Libérer la mémoire
void free_table(HashTable *table) {
for (int i = 0; i < TABLE_SIZE; i++) {
Node *current = table->buckets[i];
while (current) {
Node *temp = current;
current = current->next;
free(temp->key);
free(temp);
}
}
free(table);
}
// Main
int main() {
HashTable *table = create_table();
insert(table, "Alice", 25);
insert(table, "Bob", 30);
insert(table, "Eve", 40);
display(table);
printf("Recherche de Bob: %d\n", search(table, "Bob"));
delete(table, "Alice");
display(table);
free_table(table);
return 0;
}
7. Points Clés à Retenir
- Une fonction de hachage efficace réduit les collisions.
- Les collisions sont inĂ©vitables, mais peuvent ĂȘtre bien gĂ©rĂ©es avec des listes chaĂźnĂ©es ou des sondages.
- Toujours tester la table avec des cas limites :
- Clés similaires.
- Tableau plein.
8. Pour Aller Plus Loin
- Ătudier les autres mĂ©thodes de gestion des collisions : Open Addressing, Double Hashing.
- Implémenter des tables dynamiques qui redimensionnent leur taille automatiquement.
- Explorer les implémentations avancées comme les Bloom Filters.
Avec cette comprĂ©hension complĂšte, vous ĂȘtes prĂȘt Ă maĂźtriser les tables de hachage et Ă les appliquer dans vos projets ou entretiens techniques ! đ