Menu
Coddy logo textTech

Tablica haszująca (hash table)

Ostatnia aktualizacja

Tablica haszująca przechowuje elementy w tablicy kubełków. Aby umieścić lub znaleźć klucz, przepuszcza go przez funkcję haszującą i bierze wynik modulo liczba kubełków, co daje indeks kubełka w O(1). Kliknij odtwarzanie powyżej i zobacz, jak każdy klucz jest haszowany do kubełka i zapisywany, a wyszukiwanie przeskakuje prosto do właściwego kubełka.

Gdy dwa klucze trafiają do tego samego kubełka, czyli dochodzi do kolizji, ta wizualizacja używa metody łańcuchowej: każdy kubełek przechowuje małą listę, a kolidujące klucze są do niej dopisywane. Dopóki tablica nie jest zbyt pełna (niski współczynnik zapełnienia), a hasz dobrze rozkłada klucze, łańcuchy pozostają krótkie, a wstawianie, wyszukiwanie i usuwanie działają średnio w O(1).

Złożoność czasowa

OperacjaŚrednioNajgorszy przypadek
WstawianieO(1)O(n) (wszystkie klucze kolidują)
WyszukiwanieO(1)O(n)
UsuwanieO(1)O(n)
PamięćO(n)O(n)

Kluczowe pojęcia

PojęcieZnaczenie
Funkcja haszującaOdwzorowuje klucz na indeks kubełka
KolizjaDwa klucze trafiają do tego samego kubełka
Metoda łańcuchowaKażdy kubełek przechowuje listę kolidujących wpisów
Adresowanie otwarteAlternatywa: szukanie kolejnego wolnego miejsca
Współczynnik zapełnieniawpisy / kubełki: decyduje o zmianie rozmiaru

Przykład krok po kroku

Wstawianie kluczy 20, 34, 9, 13 do tablicy z 7 kubełkami przy użyciu hash(k) = k % 7:

KrokStrukturaDziałanie
Wstaw 20kubełek 6: [20]20 % 7 = 6, kubełek 6 pusty, zapisz 20
Wstaw 34kubełek 6: [20, 34]34 % 7 = 6, kolizja z 20, dopisz 34 do łańcucha
Wstaw 9kubełek 2: [9]9 % 7 = 2, kubełek 2 pusty, zapisz 9
Wstaw 13kubełek 6: [20, 34, 13]13 % 7 = 6, kolizja, dopisz 13 do łańcucha kubełka 6
Wyszukaj 34kubełek 6: [20, 34, 13]34 % 7 = 6, przejrzyj łańcuch: 20 nie, 34 pasuje: znaleziono

Kiedy używać tablicy haszującej

Używaj, gdyUnikaj, gdy
Potrzebujesz średnio O(1) na wstawianie, wyszukiwanie i usuwanie po kluczuPotrzebujesz kluczy w posortowanej kolejności: użyj zrównoważonego drzewa BST
Klucze nie mają użytecznego porządku i sprawdzasz tylko dokładną przynależnośćPotrzebujesz zapytań o zakres lub szukania najbliższego klucza
Stać cię na trochę dodatkowej pamięci na kubełki i niski współczynnik zapełnieniaPamięci jest skrajnie mało i liczy się każdy bajt
Istnieje dobra funkcja haszująca dla twojego typu kluczaCzas odpowiedzi w najgorszym przypadku musi być ograniczony: kolizje dają O(n)

Hash Table: kod

Przejrzysta, gotowa do uruchomienia implementacja algorytmu Hash Table w językach: Python, JavaScript, Java, C++, C. Wybierz język, skopiuj kod albo otwórz go od razu w edytorze online Coddy.

Hash Table: kod (Python)

Python
1class HashTable:2    def __init__(self, size=8):3        self.size = size4        self.buckets = [[] for _ in range(size)]5
6    def _index(self, key):7        # Hash the key to a bucket; different keys can collide8        return sum(ord(ch) for ch in key) % self.size9
10    def set(self, key, value):11        bucket = self.buckets[self._index(key)]12        for i, (k, _) in enumerate(bucket):13            if k == key:14                bucket[i] = (key, value)  # update existing key15                return16        bucket.append((key, value))  # chain on collision17
18    def get(self, key):19        for k, v in self.buckets[self._index(key)]:20            if k == key:21                return v22        raise KeyError(key)23
24    def delete(self, key):25        bucket = self.buckets[self._index(key)]26        for i, (k, _) in enumerate(bucket):27            if k == key:28                del bucket[i]29                return30        raise KeyError(key)31
32
33table = HashTable()34table.set("apple", 3)35table.set("banana", 7)36table.set("cherry", 5)37
38print("apple  ->", table.get("apple"))39print("banana ->", table.get("banana"))40table.set("apple", 10)41print("apple  ->", table.get("apple"))42table.delete("banana")43print("bucket sizes:", [len(b) for b in table.buckets])
Uruchom ten kod w edytorze Python online

Tablica haszująca: najczęstsze pytania

Czym jest kolizja haszy i jak się ją rozwiązuje?
Kolizja występuje, gdy dwa różne klucze trafiają do tego samego kubełka. Dwa popularne rozwiązania to metoda łańcuchowa (każdy kubełek przechowuje listę, a kolidujące klucze są do niej dopisywane, jak tutaj) i adresowanie otwarte (szukanie kolejnego pustego miejsca w tablicy). Obie zachowują poprawność wyszukiwania; różnią się układem pamięci i wydajnością przy dużym zapełnieniu.
Jaka jest złożoność czasowa tablicy haszującej?
Wstawianie, wyszukiwanie i usuwanie mają średnio złożoność O(1), gdy funkcja haszująca równomiernie rozkłada klucze, a współczynnik zapełnienia jest niski. W najgorszym przypadku, gdy każdy klucz koliduje w jednym kubełku, degradują się do O(n), dlatego dobra funkcja haszująca i zmiana rozmiaru mają znaczenie.
Czym jest współczynnik zapełnienia?
Współczynnik zapełnienia to liczba zapisanych wpisów podzielona przez liczbę kubełków. Gdy rośnie, łańcuchy się wydłużają, a operacje zwalniają, więc większość tablic haszujących zmienia rozmiar (przehaszowuje wpisy do większej tablicy), gdy przekroczy on próg, na przykład 0,75.
Kiedy użyć tablicy haszującej zamiast binarnego drzewa poszukiwań?
Użyj tablicy haszującej, gdy potrzebujesz tylko wyszukiwania dokładnych dopasowań i chcesz średniej szybkości O(1) bez wymogu porządku. Użyj zrównoważonego binarnego drzewa poszukiwań, gdy potrzebujesz kluczy w posortowanej kolejności, zapytań o zakres lub szukania poprzednika i następnika, czego tablica haszująca nie zrobi wydajnie. Drzewo BST daje gwarantowane operacje w O(log n), a tablica haszująca wymienia tę gwarancję na szybszą średnią wydajność.
Czym różni się metoda łańcuchowa od adresowania otwartego?
Metoda łańcuchowa przechowuje kolidujące klucze w liście przypisanej do kubełka, więc kubełek może mieć wiele wpisów, a tablica nigdy się naprawdę nie zapełnia. Adresowanie otwarte trzyma wszystko w samej tablicy i przy kolizji szuka kolejnego wolnego miejsca, co dobrze współpracuje z pamięcią podręczną, ale gwałtownie traci wydajność, gdy współczynnik zapełnienia zbliża się do 1, i wymaga ostrożnej obsługi usuwania. Metoda łańcuchowa toleruje wyższe zapełnienie; adresowanie otwarte zużywa pamięć oszczędniej.
Dlaczego nie mogę polegać na tym, że tablica haszująca zachowa kolejność wstawiania lub sortowania kluczy?
Funkcja haszująca celowo rozrzuca klucze po kubełkach, aby uniknąć skupisk, więc kolejność iteracji odzwierciedla układ kubełków, a nie kolejność wstawiania czy sortowania. Jeśli potrzebujesz porządku, użyj uporządkowanej mapy lub drzewa albo struktury takiej jak mapa zachowująca kolejność wstawiania (np. dict w Pythonie zachowuje kolejność wstawiania, ale to gwarancja języka, a nie wrodzona cecha tablicy haszującej). Nigdy nie zakładaj, że kolejność iteracji odpowiada kolejności wstawiania kluczy, chyba że język wyraźnie to obiecuje.
Ilustracja języków programowania w Coddy

Opanuj algorytmy z Coddy

ZACZNIJ