Bölüm 14: Hash Tabloları
Giriş
Öğrenme Çıktıları
Anahtardan konumu hesaplayarak ortalama O(1) arama yapan hash tablosunu tanımlamak.
Bölme, çarpma, katlama ve dizgi hash'ini hesaplamak; iyi fonksiyonun özelliklerini saymak.
Doğrusal/karesel yoklama, çift hash ve zincirlemeyi adım adım uygulamak.
Ekleme, arama ve silme yordamlarını malloc/free ve boş durumlarıyla yazmak.
Açık adreslemede neden silindi işareti (tombstone) gerektiğini göstermek.
Yük faktörünün maliyete etkisini ve yeniden boyutlandırmanın amortize maliyetini açıklamak.
Hash Fikri
Arama maliyetini sabite indirmek
| Yapı | Ara | Ekle | Sil | Not |
|---|---|---|---|---|
| Sırasız dizi | O(n) | O(1) | O(n) | sona eklenir, baştan taranır |
| Sıralı dizi | O(lg n) | O(n) | O(n) | ikili arama + kaydırma |
| Dengeli arama ağacı | O(lg n) | O(lg n) | O(lg n) | sıralı gezinme de verir |
| Hash tablosu | O(1)* | O(1)* | O(1)* | * ortalama; en kötü O(n) |
Hash Fikri
Önce en basiti: doğrudan adresleme tablosu
typedef struct { int var; char ad[16]; } kayit; kayit T[10]; /* anahtar = indis */ kayit *da_ara(int k) { return T[k].var ? &T[k] : NULL; }
Öğrenci numarası gibi 9 haneli anahtarlarda |U| = 109: tablo sığmaz, çoğu boş kalır.
Anahtarı küçük bir aralığa eşleyen bir hash fonksiyonu h: U → {0, …, m−1}.
Hash Fikri
Anahtar → hash → kova
| Terim | Anlamı |
|---|---|
| kova / yuva (bucket, slot) | tablonun bir konumu, 0 … m−1 |
| ev adresi | h(k): anahtarın ilk bakılan yeri |
| çakışma (collision) | k₁ ≠ k₂ ama h(k₁) = h(k₂) |
| eş anlamlılar (synonyms) | aynı ev adresine düşen anahtarlar |
| yük faktörü α | n / m (dolu oranı) |
Hash Fikri
Soyut veri tipi: sözlük (dictionary)
typedef struct { int anahtar; /* + değer */ } oge; bool hash_ekle(oge x); /* varsa günceller */ oge *hash_ara(int anahtar); /* yoksa NULL */ bool hash_sil(int anahtar); /* yoksa false */
Anahtar → değer eşlemesi; her anahtar en çok bir kez bulunur.
Derleyicide sembol tablosu (Horowitz'in çıkış örneği), önbellek, tekrar denetimi, küme gerçeklemesi.
hsearch vardır); bu yüzden C programcısı kendi tablosunu yazmayı bilmelidir.Hash Fonksiyonları
İyi bir hash fonksiyonunun özellikleri
Hesaplaması O(anahtar uzunluğu); birkaç aritmetik işlem.
Anahtarlar m kovaya olabildiğince eşit dağılmalı (simple uniform hashing varsayımı).
Yalnızca son basamaklara bakan fonksiyonlar örüntülü veride kümelenir.
Aynı anahtar her zaman aynı kovaya gider; rastgelelik yalnızca fonksiyon seçilirken olabilir.
Hash Fonksiyonları
Bölme yöntemi: h(k) = k mod m
| k | 10 | 20 | 30 | 40 | 50 | 60 | 17 | 27 |
|---|---|---|---|---|---|---|---|---|
| k mod 10 | 0 | 0 | 0 | 0 | 0 | 0 | 7 | 7 |
| k mod 11 | 10 | 9 | 8 | 7 | 6 | 5 | 6 | 5 |
- m = 10: son basamak dışında her şey yok sayılır; 10, 20, …, 60 aynı kovaya düşer.
- m için 2'nin kuvvetine yakın olmayan bir asal sayı seçmek iyi bir kuraldır (CLRS).
- m = 2p ise h(k) yalnızca k'nin en düşük p bitidir: kaçınılmalı.
unsigned h_bolme(unsigned k, unsigned m) { return k % m; }
C'de % negatif sayıda negatif sonuç verebilir: anahtar unsigned tutulur ya da ((k % m) + m) % m kullanılır.
Hash Fonksiyonları
Çarpma yöntemi: h(k) = ⌊m · (k·A mod 1)⌋
- 0 < A < 1 sabit; k·A'nın kesir kısmı m ile çarpılır.
- m'nin seçimi kritik değildir; m = 2p seçilebilir.
- Knuth'un önerisi A ≈ (√5 − 1)/2 = 0,6180339887…
- Örnek (CLRS): k = 123456, p = 14 (m = 16384) → h(k) = 67.
/* w = 32 bit, m = 2^p; s = A * 2^32 */ uint32_t h_carpma(uint32_t k, int p) { const uint32_t s = 2654435769u; return (uint32_t)(k * s) >> (32 - p); }
<stdint.h> gerekir.)Hash Fonksiyonları
Katlama (folding) ve orta-kare yöntemleri
Anahtar x = 12320324111220 üç haneli parçalara bölünüp toplanır (Horowitz örneği):
Parçalar olduğu gibi toplanır: 699.
Çift sıradaki parçalar ters çevrilir (kâğıt katlar gibi): 897.
k² hesaplanır, ortadaki r bit alınır: m = 2r. Orta bitler k'nin tüm bitlerine bağlıdır.
Sonuç tablo boyutundan büyükse ayrıca mod m alınır.
Hash Fonksiyonları
Dizgi anahtarlar: polinom (Horner) hash
unsigned dizgi_hash(const char *s, unsigned m) { unsigned h = 0; for (; *s != '\0'; s++) h = (h * 31 + (unsigned char)*s) % m; return h; }
unsigned long djb2(const char *s) { /* Bernstein */ unsigned long h = 5381; int c; while ((c = (unsigned char)*s++) != 0) h = h * 33 + c; return h; /* sonra: h % m */ }
Harf kodlarını toplamak "abc", "cba" gibi anagramları aynı kovaya atar.
h = c₀·31n−1 + … + cn−1: Horner kuralıyla her karakterde bir çarpma. Konum bilgisi hash'e girer.
Her adımda % m almak değerleri küçük tutar; unsigned taşması C'de tanımlıdır (mod 2w).
Dizgi hash'i · kodu adım adım çalıştır
Kod izleme: dizgi_hash("kedi", 11)
dizgi_hash("kedi", 11) çağrılır.
h = 0 ile başlanır.
*s = 'k' ≠ '\0' → döngü sürer.
h = (0·31 + 107) % 11 = 107 % 11 = 8.
*s = 'e' ≠ '\0' → döngü sürer.
h = (8·31 + 101) % 11 = 349 % 11 = 8.
*s = 'd' ≠ '\0' → döngü sürer.
h = (8·31 + 100) % 11 = 348 % 11 = 7.
*s = 'i' ≠ '\0' → döngü sürer.
h = (7·31 + 105) % 11 = 322 % 11 = 3.
*s == '\0': dizgi bitti.
return 3: "kedi" anahtarının ev adresi 3. Maliyet dizgi uzunluğuyla doğrusal.
unsigned dizgi_hash(const char *s, unsigned m) { unsigned h = 0; for (; *s != '\0'; s++) h = (h * 31 + (unsigned char)*s) % m; return h; }
Denetim: tam polinom 3287903 ve 3287903 mod 11 = 3; her adımda mod almak sonucu değiştirmez.
Açık Adresleme
Çakışma: iki temel çözüm yolu
23 kişilik bir sınıfta iki kişinin doğum gününün aynı olma olasılığı ≈ %50.7. m = 365 yuvaya 23 anahtar: çakışma şaşırtıcı derecede erken gelir.
Tüm öğeler tablonun içinde; α ≤ 1. Yoklama (probe) dizisiyle boş yuva aranır.
Her kova bir bağlı listenin başı; α > 1 olabilir. Ekleme O(1).
Açık Adresleme
Doğrusal yoklama: h(k, i) = (h(k) + i) mod m
Sırasıyla 22, 1, 13, 11, 24, 33 eklenecek. Ev adresleri: 0, 1, 2, 0, 2, 0.
22 → ev 0: yuva 0.
1 → ev 1: yuva 1.
13 → ev 2: yuva 2.
11: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
11: yuva 1 dolu (1) → sıradaki yuvaya bakılır.
11: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
11 → ev 0, yoklama dizisi 0, 1, 2, 3: yuva 3.
24: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
24: yuva 3 dolu (11) → sıradaki yuvaya bakılır.
24 → ev 2, yoklama dizisi 2, 3, 4: yuva 4.
33: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
33: yuva 1 dolu (1) → sıradaki yuvaya bakılır.
33: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
33: yuva 3 dolu (11) → sıradaki yuvaya bakılır.
33: yuva 4 dolu (24) → sıradaki yuvaya bakılır.
33 → ev 0, yoklama dizisi 0, 1, 2, 3, 4, 5: yuva 5.
Birincil kümelenme (primary clustering): dolu yuvalar art arda bir blok oluşturur; bloğa düşen her yeni anahtar bloğu büyütür. 33 için 6 deneme gerekti.
Açık Adresleme
C ile açık adresleme: yuva durumu ve ekleme
#define M 11 typedef enum { BOS, DOLU, SILINDI } durum_t; typedef struct { int anahtar; /* anahtar >= 0 varsayılır */ durum_t durum; } yuva; yuva tablo[M]; /* global: hepsi 0 = BOS */
Global dizi sıfırla başlar; BOS = 0 olduğu için tüm yuvalar boştur.
Ev adresinden başlanır, DOLU olmayan ilk yuvaya yazılır; en çok m deneme.
ara ile denetlenmelidir.Açık adresleme · kodu adım adım çalıştır
Kod izleme: ekle(33) — doğrusal yoklama
ekle(33): tabloda 22, 1, 13, 11, 24 var (yuva 0–4 dolu blok).
Ev adresi i = 33 % 11 = 0.
d = 0 < 11: 0. deneme, yuva 0.
Yuva 0 DOLU (22) → sonraki: i = (0+1) % 11.
d = 1 < 11: 1. deneme, yuva 1.
Yuva 1 DOLU (1) → sonraki: i = (1+1) % 11.
d = 2 < 11: 2. deneme, yuva 2.
Yuva 2 DOLU (13) → sonraki: i = (2+1) % 11.
d = 3 < 11: 3. deneme, yuva 3.
Yuva 3 DOLU (11) → sonraki: i = (3+1) % 11.
d = 4 < 11: 4. deneme, yuva 4.
Yuva 4 DOLU (24) → sonraki: i = (4+1) % 11.
d = 5 < 11: 5. deneme, yuva 5.
Yuva 5 BOS → buraya yazılacak.
tablo[5] = {33, DOLU}.
return 5: 6 yoklama. Birincil kümelenme yüzünden ev adresinden 5 yuva uzağa düştü.
int ekle(int k) { int i = k % M; /* ev adresi */ for (int d = 0; d < M; d++, i = (i + 1) % M) { if (tablo[i].durum != DOLU) { /* BOS/SILINDI */ tablo[i].anahtar = k; tablo[i].durum = DOLU; return i; } } return -1; /* tablo dolu */ }
Açık adresleme · kodu adım adım çalıştır
Kod izleme: ara(24) ve ara(44)
ara(24) çağrılır.
Ev adresi i = 24 % 11 = 2.
Yuva 2: başka anahtar (13) → i = 3.
Yuva 3: başka anahtar (11) → i = 4.
Yuva 4 DOLU ve anahtar 24 → return 4 (3 yoklama).
ara(44) çağrılır.
Ev adresi i = 44 % 11 = 0.
Yuva 0: başka anahtar (22) → i = 1.
Yuva 1: başka anahtar (1) → i = 2.
Yuva 2: başka anahtar (13) → i = 3.
Yuva 3: başka anahtar (11) → i = 4.
Yuva 4: başka anahtar (24) → i = 5.
Yuva 5: başka anahtar (33) → i = 6.
Yuva 6 BOS: 44 olsaydı en geç burada olurdu → return −1 (yok).
int ara(int k) { int i = k % M; for (int d = 0; d < M; d++, i = (i + 1) % M) { if (tablo[i].durum == BOS) return -1; if (tablo[i].durum == DOLU && tablo[i].anahtar == k) return i; } return -1; } bool sil(int k) { int i = ara(k); if (i < 0) return false; tablo[i].durum = SILINDI; /* silindi işareti */ return true; }
Açık Adresleme
Karesel yoklama: h(k, i) = (h(k) + i²) mod m
Aynı anahtarlar: 22, 1, 13, 11, 24, 33. Denemeler ev adresinden 1, 4, 9, 16… uzağa sıçrar.
22 → ev 0: yuva 0.
1 → ev 1: yuva 1.
13 → ev 2: yuva 2.
11: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
11: yuva 1 dolu (1) → sıradaki yuvaya bakılır.
11 → ev 0, yoklama dizisi 0, 1, 4: yuva 4.
24: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
24 → ev 2, yoklama dizisi 2, 3: yuva 3.
33: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
33: yuva 1 dolu (1) → sıradaki yuvaya bakılır.
33: yuva 4 dolu (11) → sıradaki yuvaya bakılır.
33 → ev 0, yoklama dizisi 0, 1, 4, 9: yuva 9.
Birincil kümelenme kalkar; ev adresi aynı olanlar yine aynı diziyi izler (ikincil kümelenme). m asal ve α ≤ 1/2 ise boş yuva bulunması garantidir.
Açık Adresleme
Çift hash: h(k, i) = (h₁(k) + i · h₂(k)) mod m
Adım boyu anahtara göre değişir: 11 → h₂ = 2, 24 → h₂ = 5, 33 → h₂ = 4.
22 → ev 0: yuva 0.
1 → ev 1: yuva 1.
13 → ev 2: yuva 2.
11: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
11: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
11 → ev 0, yoklama dizisi 0, 2, 4: yuva 4.
24: yuva 2 dolu (13) → sıradaki yuvaya bakılır.
24 → ev 2, yoklama dizisi 2, 7: yuva 7.
33: yuva 0 dolu (22) → sıradaki yuvaya bakılır.
33: yuva 4 dolu (11) → sıradaki yuvaya bakılır.
33 → ev 0, yoklama dizisi 0, 4, 8: yuva 8.
h₂(k) hiçbir zaman 0 olmamalı ve m ile aralarında asal olmalı (m asal seçilirse kendiliğinden sağlanır). Yoklama dizisi anahtara özgü olduğundan kümelenme en azdır.
Açık Adresleme
Aynı tablo, üç yoklama: 33 nereye düşer?
| Yöntem | i. deneme | 33'ün yoklama dizisi | Deneme |
|---|---|---|---|
| Doğrusal | h + i | 0, 1, 2, 3, 4, 5 | 6 |
| Karesel | h + i² | 0, 1, 4, 9 | 4 |
| Çift hash | h₁ + i·h₂ | 0, 4, 8 | 3 |
| Yöntem | Kümelenme | Farklı yoklama dizisi sayısı |
|---|---|---|
| Doğrusal | birincil (bloklar) | m |
| Karesel | ikincil (aynı ev adresi) | m |
| Çift hash | en az | ≈ m² |
Zincirleme
Zincirleme (separate chaining): kova başına bir liste
#define MZ 5 typedef struct dugum { int anahtar; struct dugum *sonraki; } dugum; dugum *kova[MZ]; /* hepsi NULL */
bool zincir_ekle(int k) { int i = k % MZ; dugum *yeni = malloc(sizeof *yeni); if (yeni == NULL) return false; /* bellek yok */ yeni->anahtar = k; yeni->sonraki = kova[i]; /* başa ekle */ kova[i] = yeni; return true; }
Liste sırası önemsiz: başa ekleme O(1). (Tekrar istenmiyorsa önce aranmalı.)
Yalnızca kendi kovasının listesinde gezinilir: ortalama 1 + α/2 düğüm.
Bağlı listeden silmek yeter; silindi işareti gerekmez.
Zincirleme · kodu adım adım çalıştır
Kod izleme: zincir_ekle 12, 7, 22 (hepsi kova 2)
zincir_ekle(12).
i = 12 % 5 = 2.
Yeni düğüm için bellek alınır; NULL değil, devam.
anahtar = 12; sonraki = kova[2] (NULL): yeni düğüm eski başı gösterir.
kova[2] = yeni: 12 listenin başında. O(1).
zincir_ekle(7).
i = 7 % 5 = 2.
Yeni düğüm için bellek alınır; NULL değil, devam.
anahtar = 7; sonraki = kova[2] (&düğüm(12)): yeni düğüm eski başı gösterir.
kova[2] = yeni: 7 listenin başında. O(1).
zincir_ekle(22).
i = 22 % 5 = 2.
Yeni düğüm için bellek alınır; NULL değil, devam.
anahtar = 22; sonraki = kova[2] (&düğüm(7)): yeni düğüm eski başı gösterir.
kova[2] = yeni: 22 listenin başında. O(1).
bool zincir_ekle(int k) { int i = k % MZ; dugum *yeni = malloc(sizeof *yeni); if (yeni == NULL) return false; /* bellek yok */ yeni->anahtar = k; yeni->sonraki = kova[i]; /* başa ekle */ kova[i] = yeni; return true; }
Zincirleme · kodu adım adım çalıştır
Kod izleme: zincir_ara(12) ve zincir_ara(17)
zincir_ara(12): kova 12 % 5 = 2.
p = kova[2]: listenin başı (22).
p->anahtar = 22 ≠ 12 → ilerlenir.
p = p->sonraki → düğüm(7).
p->anahtar = 7 ≠ 12 → ilerlenir.
p = p->sonraki → düğüm(12).
p->anahtar == 12: döngü koşulu yanlış, durulur.
return p: bulundu (3 düğüm gezildi).
zincir_ara(17): kova 17 % 5 = 2.
p = kova[2]: listenin başı (22).
p->anahtar = 22 ≠ 17 → ilerlenir.
p = p->sonraki → düğüm(7).
p->anahtar = 7 ≠ 17 → ilerlenir.
p = p->sonraki → düğüm(12).
p->anahtar = 12 ≠ 17 → ilerlenir.
p = p->sonraki → NULL.
p == NULL: liste bitti, döngüden çıkılır.
return NULL: 17 tabloda yok. Tüm zincir (3 düğüm) tarandı.
dugum *zincir_ara(int k) { dugum *p = kova[k % MZ]; while (p != NULL && p->anahtar != k) p = p->sonraki; return p; /* NULL: yok */ }
Zincirleme · kodu adım adım çalıştır
Kod izleme: zincir_sil(7)
zincir_sil(7): 7 zincirin ortasında (22 → 7 → 12).
i = 7 % 5 = 2.
p listenin başında, onceki = NULL.
22 ≠ 7 → döngüye girilir.
onceki = 22'nin düğümü, p = 7'nin düğümü.
p->anahtar == 7 → döngü biter.
p NULL değil: anahtar bulundu.
onceki NULL değil → onceki->sonraki = p->sonraki: 22 artık doğrudan 12'yi gösterir; 7 zincirden çıktı.
free(p): düğümün belleği geri verilir. Bundan sonra p kullanılmamalı (sarkan işaretçi).
return true. Baştaki düğüm silinseydi (onceki == NULL) kova[i] güncellenirdi.
bool zincir_sil(int k) { int i = k % MZ; dugum *p = kova[i], *onceki = NULL; while (p != NULL && p->anahtar != k) { onceki = p; p = p->sonraki; } if (p == NULL) return false; /* yok */ if (onceki == NULL) kova[i] = p->sonraki; else onceki->sonraki = p->sonraki; free(p); return true; }
Yük Faktörü ve Silme
Silmede tuzak: yuvayı boşaltmak aramayı keser
13'ü sileceğiz. 24'ün ev adresi 2; 24, 2 ve 3 dolu olduğu için 4'e yerleşmişti.
Yuvayı BOS yaparsak…
…ara(24) ev adresinde boş yuva görür ve “yok” der. Oysa 24 tabloda!
Doğrusu yuvayı silindi işareti (SILINDI) ile işaretlemektir.
Arama silindi işaretini boş saymaz, devam eder; ekleme ise silindi işaretini yeniden kullanabilir.
Silindi işareti · kodu adım adım çalıştır
Kod izleme: sil(13), ardından ara(24)
sil(13) çağrılır; önce yeri bulunmalı.
Ev adresi i = 13 % 11 = 2.
Yuva 2 DOLU ve anahtar 13 → return 2 (1 yoklama).
ara 2 döndürdü; i ≥ 0, silinecek öğe var.
tablo[2].durum = SILINDI: silindi işareti.
return true.
Ev adresi i = 24 % 11 = 2.
Yuva 2: silindi işareti: silinmiş, aramaya devam → i = 3.
Yuva 3: başka anahtar (11) → i = 4.
Yuva 4 DOLU ve anahtar 24 → return 4 (3 yoklama).
int ara(int k) { int i = k % M; for (int d = 0; d < M; d++, i = (i + 1) % M) { if (tablo[i].durum == BOS) return -1; if (tablo[i].durum == DOLU && tablo[i].anahtar == k) return i; } return -1; } bool sil(int k) { int i = ara(k); if (i < 0) return false; tablo[i].durum = SILINDI; /* silindi işareti */ return true; }
Yük Faktörü ve Silme
Yük faktörü α = n/m ve beklenen yoklama sayısı
| α | Zincir ✓ | Zincir ✗ | Doğrusal ✓ | Doğrusal ✗ | Düzgün* ✓ | Düzgün* ✗ |
|---|---|---|---|---|---|---|
| 0,25 | 1,12 | 1,25 | 1,2 | 1,4 | 1,15 | 1,3 |
| 0,5 | 1,25 | 1,50 | 1,5 | 2,5 | 1,39 | 2,0 |
| 0,75 | 1,38 | 1,75 | 2,5 | 8,5 | 1,85 | 4,0 |
| 0,9 | 1,45 | 1,90 | 5,5 | 50,5 | 2,56 | 10,0 |
✓ başarılı, ✗ başarısız arama. Formüller: zincirleme 1 + α/2 ve 1 + α (Θ(1 + α)); doğrusal yoklama (Knuth) ½(1 + 1/(1−α)) ve ½(1 + 1/(1−α)²); * düzgün hash varsayımıyla açık adresleme (1/α)·ln(1/(1−α)) ve 1/(1−α) (CLRS).
Yük Faktörü ve Silme
Yeniden boyutlandırma (rehashing)
typedef struct { yuva *a; /* m yuvalık dinamik dizi */ int m, n; /* boyut, dolu yuva sayısı */ } Tablo; /* ekledikten sonra: */ if (2 * t->n > t->m) /* α > 1/2 */ yeniden_boyutla(t, sonraki_asal(2 * t->m));
h(k) = k mod m, m'ye bağlıdır: öğeler yeni tabloda başka yuvalara düşer; kopyalamak yetmez.
Boyut her seferinde iki katına çıkınca n ekleme için toplam kopyalama ≤ 2n: ekleme başına sabit.
Yeniden hash'leme silindi işaretlerini de temizler.
Yeniden boyutlandırma · kodu adım adım çalıştır
Kod izleme: yeniden_boyutla(t, 11)
n = 3, m = 5 → α = 0,6 > 0,5: tablo 11 yuvaya büyütülecek (≈ 2m, asal).
Eski dizinin adresi ve boyutu saklanır.
calloc sıfırlanmış 11 yuva verir: hepsi BOS (= 0).
Bellek alındı; tablo yeni diziye bağlanır, n = 0.
eski[0] BOS: atlanır.
eski[1] BOS: atlanır.
eski[2] = 7 DOLU → yeniden hash: 7 % 11 = 7 → yuva 7. (Eski ev adresi 7 % 5 = 2 idi.)
eski[3] = 12 DOLU → yeniden hash: 12 % 11 = 1 → yuva 1. (Eski ev adresi 12 % 5 = 2 idi.)
eski[4] = 3 DOLU → yeniden hash: 3 % 11 = 3 → yuva 3. (Eski ev adresi 3 % 5 = 3 idi.)
Eski dizi free edilir. Yeni α = 3/11 ≈ 0,27.
return true. Tüm öğeler yeniden eklendi: O(n + m).
bool yeniden_boyutla(Tablo *t, int yeni_m) { yuva *eski = t->a; int eski_m = t->m; yuva *a = calloc(yeni_m, sizeof *a); /* BOS */ if (a == NULL) return false; t->a = a; t->m = yeni_m; t->n = 0; for (int i = 0; i < eski_m; i++) if (eski[i].durum == DOLU) tablo_ekle(t, eski[i].anahtar); free(eski); return true; }
Özet
Zincirleme mi, açık adresleme mi?
Zincirleme
- α > 1 olabilir; dolmaz, yavaşlar.
- Silme basit, silindi işareti yok.
- Kötü hash'e karşı daha dayanıklı.
- Düğüm başına işaretçi +
mallocmaliyeti; önbellek dostu değil.
Açık adresleme
- Ek işaretçi yok; bellek bitişik, önbellek dostu.
- α ≤ 1; α → 1 iken maliyet patlar.
- Silme için silindi işareti gerekir.
- Kümelenmeye karşı iyi yoklama (çift hash) seçilmeli.
Özet
Karmaşıklık özeti
| İşlem | Ortalama (α sabit) | En kötü | Not |
|---|---|---|---|
| ara | O(1) | O(n) | tüm anahtarlar aynı kovaya düşerse |
| ekle | O(1) | O(n) | zincirde başa ekleme her zaman O(1) |
| sil | O(1) | O(n) | açık adreslemede silindi işareti |
| yeniden boyutlandır | O(n + m) | O(n + m) | ekleme başına amortize O(1) |
| sıralı gezinme / min / max | O(n + m) | O(n lg n) | hash sıra tutmaz |
Özet
Sık yapılan hatalar
k % m'nin negatif çıkıp dizi dışına yazması.malloc sonucunu denetlememek; free sonrası düğümü kullanmak.Özet
Alıştırmalar
- m = 7, h(k) = k mod 7, doğrusal yoklama: 10, 17, 24, 3 sırayla eklenince tablo ne olur?
- Aynı anahtarları zincirleme (başa ekleme) ile yerleştirin; en uzun zincir kaç düğümdür?
- m = 11 ve karesel yoklamada 0, 1, 4, 9, 16, 25 (mod 11) yoklama dizisini yazın.
- α = 0,9 olan doğrusal yoklamalı tabloda başarısız arama kaç yoklama bekler?
"ab"ve"ba"için toplama hash'i ve 31 tabanlı polinom hash'i (mod 11) hesaplayın.- Açık adreslemede
ekle, SILINDI yuvaya yazmadan önce nedenaraçağırmalıdır?
Cevapları göster
- 10 → 3, 17 → 3 dolu → 4, 24 → 3, 4 dolu → 5, 3 → 3, 4, 5 dolu → 6: yuva 3–6 = 10, 17, 24, 3.
- Hepsi kova 3'te: 3 → 24 → 17 → 10 (4 düğüm). Kötü hash seçimi zinciri uzatır.
- 0, 1, 4, 9, 5, 3 (16 mod 11 = 5, 25 mod 11 = 3).
- ½(1 + 1/0,01) = 50,5 yoklama: α → 1 iken maliyet patlar.
- Toplama: 97 + 98 = 195 → ikisi de 195 % 11 = 8. Polinom: "ab" = 97·31 + 98 = 3105 → 3; "ba" = 98·31 + 97 = 3135 → 0.
- Aynı anahtar silindi işaretinden sonraki bir yuvada zaten olabilir; kontrol edilmezse tabloya iki kez girer.
Özet
Bölüm özeti
Anahtardan konum hesaplanır: ortalama O(1) ara/ekle/sil.
Bölme (m asal), çarpma (A ≈ 0,618), katlama, polinom dizgi hash'i.
Doğrusal, karesel, çift hash; kümelenme ve silindi işareti.
Kova başına bağlı liste; başa ekleme O(1), silme kolay.
α = n/m küçük tutulmalı; eşik aşılınca yeniden boyutlandırılır.
Sıra bilgisi yok; en kötü O(n); evrensel hash ile koruma.
Özet
Kaynaklar ve devam
- E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, Bölüm 8 (Hashing).
- T. H. Cormen vd., Introduction to Algorithms (CLRS), Bölüm 11 (Hash Tables).
- R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 14 (Hashing).
- D. E. Knuth, The Art of Computer Programming, Cilt 3, Bölüm 6.4.