Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Hash-Tabellen mit separater Verkettung in C: Ein praktischer Leitfaden für COP3502C

Lerne, wie du eine Hash-Tabelle mit separater Verkettung implementierst – mit einem praxisnahen Beispiel aus der Theaterverwaltung, das dich durch alle wichtigen Konzepte führt.

Hash-Tabelle C separate Verkettung COP3502C Hash-Funktion C Theaterinventar Programmierung C Programmierung Tutorial Datenstrukturen C verkettete Liste C Hash-Tabelle Implementierung C Programmierung Studium Hash-Tabelle Beispiel Komplexität Hash-Tabelle C Code Beispiel Inventarverwaltung C KI Datenstrukturen Gaming Inventar C

Einführung in Hash-Tabellen und separate Verkettung

Hash-Tabellen sind eine der effizientesten Datenstrukturen, um Daten schnell zu speichern und abzurufen. In diesem Tutorial konzentrieren wir uns auf die Implementierung einer Hash-Tabelle mit separater Verkettung in C – ein häufiges Thema in Kursen wie COP3502C. Stell dir vor, du verwaltest ein Theaterinventar: Du musst Artikel wie Popcorn oder Limonade kaufen, verkaufen und Preise aktualisieren. Genau das werden wir mit einer Hash-Tabelle umsetzen.

Warum separate Verkettung? Sie löst Kollisionen elegant, indem jeder Tabellenplatz auf eine verknüpfte Liste zeigt. Das ist besonders nützlich, wenn viele verschiedene Artikelnamen (Strings) als Schlüssel verwendet werden – wie in unserem Theaterszenario.

Grundlagen der Hash-Funktion

Die Hash-Funktion wandelt einen String in einen Index um. Für dieses Projekt verwenden wir eine einfache, aber effektive Funktion:

int hashfunction(char* word, int size) {
    int len = strlen(word);
    int res = 0;
    for (int i = 0; i < len; i++) {
        res = (res * 31 + word[i]) % size;
    }
    return res;
}

Diese Funktion multipliziert den bisherigen Hash mit 31 und addiert den ASCII-Wert jedes Zeichens. Das Ergebnis wird modulo der Tabellengröße genommen. Diese Methode ist schnell und verteilt die Schlüssel gleichmäßig – ähnlich wie ein Algorithmus, der in KI-gestützten Apps zur schnellen Datenabfrage verwendet wird.

Struktur der Hash-Tabelle

Wir definieren eine Struktur für die Knoten der verketteten Liste und die Hash-Tabelle selbst:

typedef struct node {
    char name[25];
    int quantity;
    int price;
    struct node* next;
} node;

typedef struct hashTable {
    node** buckets;
    int size;
} hashTable;

Jeder Knoten speichert den Artikelnamen, die Menge, den Preis und einen Zeiger auf den nächsten Knoten. Die Hash-Tabelle enthält ein Array von Buckets (jeweils Zeiger auf den Listenkopf) und die Größe.

Operationen: Kaufen, Verkaufen, Preis ändern

1. Kaufen (buy)

Beim Kaufen eines Artikels wird zuerst der Hash-Index berechnet. Dann durchsuchen wir die verknüpfte Liste an diesem Index. Wenn der Artikel bereits existiert, erhöhen wir die Menge und addieren den Kaufpreis zu den Gesamtkosten. Andernfalls erstellen wir einen neuen Knoten und fügen ihn am Anfang der Liste ein. Die Komplexität ist die Position des Knotens in der Liste (1-basiert) bzw. die Länge der Liste plus 1, wenn ein neuer Knoten erstellt wird.

void buy(hashTable* ht, char* item, int qty, int price, int* cash) {
    int index = hashfunction(item, ht->size);
    node* current = ht->buckets[index];
    int pos = 1;
    while (current != NULL) {
        if (strcmp(current->name, item) == 0) {
            current->quantity += qty;
            *cash -= price;
            printf("%s %d %d\n", item, current->quantity, *cash);
            return;
        }
        current = current->next;
        pos++;
    }
    // Neuer Knoten
    node* newNode = malloc(sizeof(node));
    strcpy(newNode->name, item);
    newNode->quantity = qty;
    newNode->price = 0; // wird später gesetzt
    newNode->next = ht->buckets[index];
    ht->buckets[index] = newNode;
    *cash -= price;
    printf("%s %d %d\n", item, qty, *cash);
}

2. Verkaufen (sell)

Beim Verkauf suchen wir den Artikel in der Liste. Wenn die gewünschte Menge größer als der Bestand ist, verkaufen wir nur den verfügbaren Bestand. Der Erlös wird zum Bargeld addiert. Die Komplexität ist die Position des Knotens.

void sell(hashTable* ht, char* item, int qty, int* cash) {
    int index = hashfunction(item, ht->size);
    node* current = ht->buckets[index];
    int pos = 1;
    while (current != NULL) {
        if (strcmp(current->name, item) == 0) {
            int sold = (qty < current->quantity) ? qty : current->quantity;
            current->quantity -= sold;
            *cash += sold * current->price;
            printf("%s %d %d\n", item, current->quantity, *cash);
            return;
        }
        current = current->next;
        pos++;
    }
}

3. Preis ändern (change_price)

Diese Operation sucht den Artikel und aktualisiert den Verkaufspreis. Die Komplexität ist wieder die Position in der Liste.

void change_price(hashTable* ht, char* item, int newPrice) {
    int index = hashfunction(item, ht->size);
    node* current = ht->buckets[index];
    while (current != NULL) {
        if (strcmp(current->name, item) == 0) {
            current->price = newPrice;
            return;
        }
        current = current->next;
    }
}

Komplexitätsberechnung und Ausgabe

Die Gesamtkomplexität ist die Summe der Komplexitäten aller Befehle. Am Ende wird der gesamte Bargeldbestand und die Gesamtkomplexität ausgegeben. In unserem Beispiel aus der Aufgabenstellung beträgt die Komplexität 10.

Vollständiges Programmgerüst

Hier ist die Grundstruktur der main.c-Datei:

/* COP 3502C Assignment 6
   This program is written by: Dein Name */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Struktur- und Funktionsdefinitionen wie oben

int main() {
    int n, cash = 100000, totalComplexity = 0;
    scanf("%d", &n);
    hashTable* ht = createTable(1000); // Größe anpassen
    for (int i = 0; i < n; i++) {
        int cmd;
        scanf("%d", &cmd);
        if (cmd == 1) {
            char item[25]; int qty, price;
            scanf("%s %d %d", item, &qty, &price);
            // Komplexität berechnen und addieren
            int comp = getComplexity(ht, item);
            totalComplexity += comp;
            buy(ht, item, qty, price, &cash);
        } else if (cmd == 2) {
            char item[25]; int qty;
            scanf("%s %d", item, &qty);
            int comp = getComplexity(ht, item);
            totalComplexity += comp;
            sell(ht, item, qty, &cash);
        } else if (cmd == 3) {
            char item[25]; int newPrice;
            scanf("%s %d", item, &newPrice);
            int comp = getComplexity(ht, item);
            totalComplexity += comp;
            change_price(ht, item, newPrice);
        }
    }
    printf("%d\n%d\n", cash, totalComplexity);
    freeTable(ht);
    return 0;
}

Trend-Beispiel: Gaming-Inventar

Stell dir vor, du verwaltest ein Inventar in einem Battle-Royale-Spiel wie Fortnite. Spieler kaufen Waffen, verkaufen Gegenstände und ändern Preise auf dem Marktplatz. Eine Hash-Tabelle mit separater Verkettung könnte die Spielerdaten in Echtzeit verwalten – ähnlich wie die Datenbanken hinter KI-gestützten Handelsplattformen. Die Effizienz ist entscheidend, wenn tausende Transaktionen pro Sekunde ablaufen.

Häufige Fehler und Tipps

  • Speicherlecks: Vergiss nicht, am Ende alle Knoten und die Tabelle freizugeben.
  • String-Vergleiche: Verwende strcmp, nicht ==.
  • Hash-Funktion: Achte darauf, dass die Größe der Tabelle eine Primzahl ist, um Kollisionen zu minimieren.
  • Komplexitätslogik: Bei neuen Knoten ist die Komplexität die Länge der Liste + 1, nicht die Position.

Fazit

Hash-Tabellen mit separater Verkettung sind ein mächtiges Werkzeug, das in vielen Bereichen eingesetzt wird – von der Inventarverwaltung im Einzelhandel bis hin zu Finanztransaktionssystemen. Mit diesem Tutorial hast du die Grundlagen verstanden und kannst sie auf deine eigenen Projekte anwenden. Viel Erfolg bei deiner COP3502C-Aufgabe!