Çarpışma Çözümleme Collision Handling

Hash tablolarında çarpışma çözümleme: Separate Chaining, Linear Probing, Quadratic Probing ve Double Hashing — kümeleme problemi, tombstone, performans karşılaştırması ile adım adım görseller ve C kodu.

🗂️ Veri Yapıları 📅 22 February 2026 👁️ 17 görüntülenme

Çarpışma Neden Kaçınılmazdır?

Hash fonksiyonu sonsuz büyüklükteki anahtar uzayını sonlu boyutlu bir diziye sıkıştırır. Pigeonhole (güvercin yuvası) prensibi gereği, eğer anahtar sayısı dizi boyutundan fazlaysa en az iki anahtar aynı indekse düşecektir. Ama pratikte anahtar sayısı dizi boyutundan az olsa bile farklı anahtarlar aynı hash değerini üretebilir — bu durum çarpışma (collision) olarak adlandırılır.

Birthday Paradox: 23 kişilik bir grupta aynı doğum gününe sahip iki kişi bulunma olasılığı %50'den fazladır. Benzer şekilde, 1000 kovalık bir hash tablosuna 38 eleman eklendiğinde çarpışma olasılığı %50'yi aşar. Çarpışma "olabilir" değil, "kesinlikle olacak" bir durumdur.

Yaklaşım 1: Zincirleme

Her kovada bir bağlı liste tut. Çarpışan elemanlar aynı kovanın listesine eklenir. Tablo dışında ek bellek kullanır.

Yaklaşım 2: Açık Adresleme

Tüm elemanlar dizi içinde kalır. Çarpışmada sistematik bir yöntemle sonraki boş kovayı ara (probing). Ek bellek yok.

Zincirleme (Separate Chaining)

En basit ve en yaygın çarpışma çözümü: her kova bir bağlı listenin başını tutar. Aynı indekse düşen tüm elemanlar bu listede saklanır. Ekleme O(1) (başa ekle), arama O(k) (k = zincir uzunluğu), silme O(k).

Tam Chaining Implementasyonu

chaining.c — tam implementasyon
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define INIT_SIZE  11
#define MAX_LOAD   0.75

typedef struct Entry {
    char* key;
    int   value;
    struct Entry* next;
} Entry;

typedef struct {
    Entry** buckets;
    int     size;      // kova sayısı
    int     count;     // eleman sayısı
} ChainTable;

unsigned int hashDJB2(const char* k, int sz) {
    unsigned long h = 5381;
    while (*k) h = h * 33 + *k++;
    return h % sz;
}

ChainTable* chainOlustur(int sz) {
    ChainTable* t = malloc(sizeof(ChainTable));
    t->size = sz; t->count = 0;
    t->buckets = calloc(sz, sizeof(Entry*));
    return t;
}

/* ── Ekleme ── */
void chainEkle(ChainTable* t, const char* key, int val) {
    unsigned int idx = hashDJB2(key, t->size);
    // Var mı? Güncelle
    for (Entry* e = t->buckets[idx]; e; e = e->next)
        if (strcmp(e->key, key) == 0) { e->value = val; return; }
    // Yeni — başa ekle
    Entry* e = malloc(sizeof(Entry));
    e->key = strdup(key); e->value = val;
    e->next = t->buckets[idx];
    t->buckets[idx] = e;
    t->count++;
}

/* ── Arama ── */
int chainAra(ChainTable* t, const char* key, bool* ok) {
    unsigned int idx = hashDJB2(key, t->size);
    for (Entry* e = t->buckets[idx]; e; e = e->next)
        if (strcmp(e->key, key) == 0) { *ok=true; return e->value; }
    *ok = false; return -1;
}

/* ── Silme ── */
bool chainSil(ChainTable* t, const char* key) {
    unsigned int idx = hashDJB2(key, t->size);
    Entry* curr = t->buckets[idx], *prev = NULL;
    while (curr) {
        if (strcmp(curr->key, key) == 0) {
            if (prev) prev->next = curr->next;
            else      t->buckets[idx] = curr->next;
            free(curr->key); free(curr);
            t->count--;
            return true;
        }
        prev = curr; curr = curr->next;
    }
    return false;
}

Adım Adım — 7 Eleman, 3 Çarpışma

Tablo boyutu = 7. Hash değerleri: A→2, B→5, C→2, D→0, E→5, F→2, G→4

7 Eleman, 3'ü İndeks 2'de, 2'si İndeks 5'te Çarpışıyor
0D:40→ ∅12F:15C:60A:85→ ∅34G:725E:33B:50→ ∅6← zincir: 3 eleman← zincir: 2 eleman
İndeks 2'de F→C→A zinciri (3 eleman). Arama sırasında en kötü durum: zincirin sonundaki A için 3 strcmp gerekir.

Zincir Uzunluğu ve Performans

İyi hash fonksiyonu ile elemanlar kovalara düzgün dağılır. n eleman ve m kova varsa ortalama zincir uzunluğu α = n/m'dir (load factor). Arama maliyeti O(1 + α): 1 hash hesabı + α karşılaştırma.

α (Load Factor)Ort. ZincirArama MaliyetiYorum
0.50.5 eleman~1.5 işlemÇok iyi
0.750.75 eleman~1.75 işlemİyi (Java HashMap eşiği)
1.01.0 eleman~2.0 işlemKabul edilebilir
2.02.0 eleman~3.0 işlemRehash zamanı
10.010 eleman~11 işlemÇok kötü — listeye dönüştü
Chaining'in gizli maliyeti — cache miss: Bağlı liste düğümleri bellekte dağınıktır. Her next pointer takibi potansiyel bir cache miss'tir. Modern CPU'larda cache miss ~100 ns sürer. 5 elemanlı bir zincir bile ardışık bellekteki 50 elemandan yavaş olabilir. Bu yüzden bazı implementasyonlar zincir yerine küçük dizi kullanır.

Açık Adresleme (Open Addressing)

Açık adreslemede bağlı liste yoktur — tüm elemanlar doğrudan dizi içinde saklanır. Bir kova dolu ise probing (araştırma) dizisi ile bir sonraki boş kova aranır. Probing dizisi şu formülle tanımlanır:

Genel probing formülü: h(key, i) = (h₁(key) + f(i)) % m
i = deneme sayısı (0, 1, 2, ...). f(i) fonksiyonu probing stratejisini belirler:
• Linear: f(i) = i
• Quadratic: f(i) = c₁·i + c₂·i²
• Double Hashing: f(i) = i · h₂(key)

Linear Probing (Doğrusal Araştırma)

En basit probing yöntemi: çarpışma olursa bir sonraki kovaya bak. Boş kova bulunana kadar sırayla ilerle. Formül: h(key, i) = (hash(key) + i) % m

Tam Linear Probing Implementasyonu

linear_probing.c
#define TABLE_SIZE  11
#define EMPTY      -1
#define DELETED    -2   // tombstone

typedef struct {
    char* key;
    int   value;
    int   state;    // EMPTY, DELETED, veya 0 (dolu)
} Slot;

typedef struct {
    Slot* slots;
    int   size;
    int   count;
} LinearTable;

/* ── Ekleme ── */
void linearEkle(LinearTable* t, const char* key, int val) {
    unsigned int idx = hashDJB2(key, t->size);

    for (int i = 0; i < t->size; i++) {
        int pos = (idx + i) % t->size;   // linear probe

        if (t->slots[pos].state == EMPTY ||
            t->slots[pos].state == DELETED) {
            // Boş veya silinmiş → yerleştir
            t->slots[pos].key   = strdup(key);
            t->slots[pos].value = val;
            t->slots[pos].state = 0;
            t->count++;
            return;
        }
        if (strcmp(t->slots[pos].key, key) == 0) {
            t->slots[pos].value = val;  // güncelle
            return;
        }
    }
}

/* ── Arama ── */
int linearAra(LinearTable* t, const char* key, bool* ok) {
    unsigned int idx = hashDJB2(key, t->size);

    for (int i = 0; i < t->size; i++) {
        int pos = (idx + i) % t->size;

        if (t->slots[pos].state == EMPTY) {
            *ok = false;  return -1;  // boş → yok
        }
        if (t->slots[pos].state != DELETED &&
            strcmp(t->slots[pos].key, key) == 0) {
            *ok = true;  return t->slots[pos].value;
        }
        // DELETED → atla, aramaya devam
    }
    *ok = false;  return -1;
}

/* ── Silme (Tombstone) ── */
bool linearSil(LinearTable* t, const char* key) {
    unsigned int idx = hashDJB2(key, t->size);

    for (int i = 0; i < t->size; i++) {
        int pos = (idx + i) % t->size;
        if (t->slots[pos].state == EMPTY) return false;
        if (t->slots[pos].state != DELETED &&
            strcmp(t->slots[pos].key, key) == 0) {
            free(t->slots[pos].key);
            t->slots[pos].state = DELETED;  // ★ tombstone!
            t->count--;
            return true;
        }
    }
    return false;
}

Adım Adım — Linear Probing ile 6 Ekleme

Tablo boyutu = 11. Ekleme sırası: hash değerleri parantez içinde.

Anahtarhash % 11Probe dizisiYerleşim
A33 (boş)→ 3
B77 (boş)→ 7
C33 (dolu!) → 4 (boş)→ 4
D44 (dolu!) → 5 (boş)→ 5
E33,4,5 (dolu!) → 6 (boş)→ 6 (3 probe!)
F77 (dolu!) → 8 (boş)→ 8
6 Eleman Sonrası — Birincil Kümeleme (Primary Clustering) Görünür
0
1
2
3
A
4
C
5
D
6
E
7
B
8
F
9
10
3-4-5-6 = küme! 7-8 = küme! Kümeler büyüdükçe yeni çarpışmalar kümeye düşer → küme daha da büyür!

Birincil Kümeleme Problemi (Primary Clustering)

Linear probing'in en büyük sorunu birincil kümeleme'dir: ardışık dolu hücreler bir "küme" oluşturur. Yeni bir eleman bu kümenin herhangi bir yerine hash'lenirse, kümenin sonuna eklenir → küme büyür → daha fazla eleman kümeye düşer → kısır döngü.

Kümeleme Etkisi — Yoğun Bölge Büyüyor
α = 0.5: Ortalama 1.5 probe (başarılı arama)
α = 0.75: Ortalama 2.5 probe
α = 0.9: Ortalama 5.5 probe
α = 0.95: Ortalama 10.5 probe — neredeyse doğrusal arama!
Formül (başarılı arama): ½(1 + 1/(1-α)). α yükseldikçe performans hızla çöker.
Linear Probing'in avantajı: Kümeleme sorununa rağmen, ardışık bellek erişimi sayesinde cache performansı çok iyidir. Modern CPU'larda cache line 64 byte'tır — bir probe'da birden fazla kova aynı anda cache'e yüklenir. Bu yüzden düşük α'da linear probing tüm yöntemlerden hızlıdır. Python dict ve Rust HashMap bu avantajı kullanır.

Tombstone (Mezar Taşı) Mekanizması

Open addressing'de silinen eleman doğrudan NULL yapılamaz — çünkü probe zinciri kopar. Çözüm: silinen hücreye özel DELETED (tombstone) işareti konur.

Tombstone Olmadan Silme → Arama Bozulur!
Başlangıç: A(3), C(3→4), E(3→4→5→6)
3
A
4
C
5
6
E
C'yi sil
→ NULL yap
NULL yapıldı → search(E) BOZULDU!
3
A
4
5
6
E
search(E): 3(A)→4(∅) DUR! → "E yok" ← YANLIŞ!
Tombstone ile doğru çözüm
3
A
4
DEL
5
6
E
search(E): 3(A)→4(DEL:atla)→5(∅)... Bekle — E aslında 6'da!
DELETED üzerinden atlanır ama arama durdurulmaz. Sadece EMPTY gördüğünde "yok" denir.

Quadratic Probing (Karesel Araştırma)

Linear probing'in birincil kümeleme sorununu çözmek için adım boyutunu sabit 1 yerine karesel artıran yöntemdir. Formül:

Quadratic Probing formülü: h(key, i) = (hash(key) + c₁·i + c₂·i²) % m
Yaygın basit form: h(key, i) = (hash(key) + i²) % m
Probe dizisi: +0, +1, +4, +9, +16, +25, ... → dolu bölgeleri atlayarak ilerler.

Adım Adım — Quadratic Probing ile Ekleme

Tablo boyutu = 11. Ekleme: A→3, B→7, C→3, D→3, E→3 (hepsi indeks 3'e hash'leniyor).

AnahtarhashProbe dizisi (i², mod 11)Yerleşim
A3i=0: (3+0)%11 = 3 boş→ 3
B7i=0: 7 boş→ 7
C3i=0: 3 dolu → i=1: (3+1)%11 = 4 boş→ 4
D3i=0: 3 dolu → i=1: 4 dolu → i=2: (3+4)%11 = 7 dolu → i=3: (3+9)%11 = 1 boş→ 1
E33,4,7,1 dolu → i=4: (3+16)%11 = 8 boş→ 8
Quadratic vs Linear — Aynı Hash Değerleri, Farklı Dağılım
Linear Probing → Küme!
A
C
D
E
B
3-4-5-6-7: 5 ardışık dolu → küme
Quadratic Probing → Dağılmış
D
A
C
B
E
1, 3, 4, 7, 8: dağınık → küme yok!
quadratic probing — ekleme
void quadEkle(LinearTable* t, const char* key, int val) {
    unsigned int h = hashDJB2(key, t->size);

    for (int i = 0; i < t->size; i++) {
        int pos = (h + i * i) % t->size;  // ★ i² adım

        if (t->slots[pos].state == EMPTY ||
            t->slots[pos].state == DELETED) {
            t->slots[pos].key   = strdup(key);
            t->slots[pos].value = val;
            t->slots[pos].state = 0;
            t->count++;
            return;
        }
        if (strcmp(t->slots[pos].key, key) == 0) {
            t->slots[pos].value = val;
            return;
        }
    }
}
Quadratic Probing'in tuzağı — İkincil Kümeleme (Secondary Clustering): Aynı hash değerine sahip tüm anahtarlar aynı probe dizisini takip eder: h, h+1, h+4, h+9, ... Bu, birincil kümelemeden hafiftir ama yine verimsizlik yaratır. Ayrıca tablo boyutu asal ve α < 0.5 değilse tüm kovalar ziyaret edilemeyebilir → yer olsa bile eleman eklenemez!
Quadratic Probing'in çalışma garantisi: Tablo boyutu asal sayı ve α < 0.5 ise, ilk ⌈m/2⌉ probe farklı kovalara düşer → boş kova bulunması garantidir. Bu yüzden quadratic probing kullanan tablolarda rehash eşiği genellikle 0.5'tir.

Double Hashing (Çift Hash)

Hem birincil hem ikincil kümelemeyi çözen en güçlü open addressing yöntemidir. Adım boyutu sabit veya karesel değil, ikinci bir hash fonksiyonu tarafından belirlenir — böylece her anahtar kendine özgü bir probe dizisi üretir.

Double Hashing formülü:
h(key, i) = (h₁(key) + i × h₂(key)) % m

h₁ = birincil hash (indeks belirler)
h₂ = ikincil hash (adım boyutunu belirler)
Kural: h₂(key) asla 0 olmamalı (sonsuz döngü!) ve m ile aralarında asal olmalı (tüm kovaları ziyaret garanti).
Yaygın formül: h₂(key) = ASAL - (key % ASAL) — ASAL < m olan bir asal sayı.

Adım Adım — Double Hashing ile Ekleme

m = 11, h₁ = key % 11, h₂ = 7 - (key % 7). Anahtarlar: 47, 58, 69, 80 (hepsi h₁ = 3).

Keyh₁h₂Probe dizisiYer
4737-(47%7) = 7-5 = 2i=0: 3 (boş)→ 3
5837-(58%7) = 7-2 = 53 dolu → i=1: (3+5)%11 = 8 (boş)→ 8
6937-(69%7) = 7-6 = 13 dolu → i=1: (3+1)%11 = 4 (boş)→ 4
8037-(80%7) = 7-3 = 43 dolu → i=1: (3+4)%11 = 7 (boş)→ 7
Double Hashing — Aynı h₁ ama Farklı Adım Boyutları → Mükemmel Dağılım
0
1
2
47
69
5
6
80
58
9
10
4 anahtar aynı h₁=3 → ama h₂ farklı (2, 5, 1, 4) → hiç kümeleme yok!
47: adım 2 → 3,5,7,9...   58: adım 5 → 3,8,2,7...   69: adım 1 → 3,4,5,6...   80: adım 4 → 3,7,0,4...
double hashing — ekleme + arama
#define PRIME2  7  // m'den küçük asal

unsigned int hash2(int key) {
    return PRIME2 - (key % PRIME2);  // asla 0 olmaz ✓
}

void doubleEkle(LinearTable* t, int key, int val) {
    unsigned int h1 = key % t->size;
    unsigned int h2 = hash2(key);

    for (int i = 0; i < t->size; i++) {
        int pos = (h1 + i * h2) % t->size; // ★ double hash

        if (t->slots[pos].state == EMPTY ||
            t->slots[pos].state == DELETED) {
            t->slots[pos].value = val;
            t->slots[pos].state = 0;
            t->count++;
            return;
        }
    }
}

int doubleAra(LinearTable* t, int key, bool* ok) {
    unsigned int h1 = key % t->size;
    unsigned int h2 = hash2(key);

    for (int i = 0; i < t->size; i++) {
        int pos = (h1 + i * h2) % t->size;
        if (t->slots[pos].state == EMPTY)
            { *ok = false; return -1; }
        if (t->slots[pos].state != DELETED &&
            t->slots[pos].value == key) // int key örneği
            { *ok = true; return t->slots[pos].value; }
    }
    *ok = false; return -1;
}

3 Probing Yönteminin Probe Dizisi Karşılaştırması

Aynı 4 anahtar (hepsi h₁ = 3), tablo boyutu 11:

AnahtarLinear (+1)Quadratic (+i²)Double (×h₂)
1. (h=3)333
2. (h=3)44 (3+1)8 (3+5)
3. (h=3)57 (3+4)4 (3+1)
4. (h=3)61 (3+9)7 (3+4)
Dağılım3,4,5,6 — küme!3,4,7,1 — daha iyi3,8,4,7 — en iyi!
Double Hashing neden en iyisi? Her anahtarın kendine özgü adım boyutu (h₂) vardır. Linear'da herkes +1, Quadratic'te herkes +i² adım atar — ama Double Hashing'de aynı h₁'e sahip anahtarlar bile farklı yollar izler. Bu, elemanların tabloya mümkün olan en düzgün şekilde dağılmasını sağlar.

4 Yöntemin Karşılaştırması

Performans — Ortalama Probe Sayısı

Başarılı arama için ortalama probe sayısı (teorik):

αChainingLinearQuadraticDouble Hash
0.251.131.171.151.15
0.501.251.501.441.39
0.751.382.502.011.85
0.901.455.503.522.56
0.951.4810.505.813.15
0.991.5050.504.65
Chaining: 1 + α/2.   Linear: ½(1 + 1/(1-α)).   Double: -ln(1-α)/α.   α yükseldikçe linear feci şekilde kötüleşir.
α = 0.90'da Performans Farkı — Aynı Arama, 4 Farklı Sonuç
Chaining
1.45
probe
Double
2.56
probe
Quadratic
3.52
probe
Linear
5.50
probe

Genel Karşılaştırma Tablosu

ÖzellikChainingLinearQuadraticDouble
KümelemeYok ✓Birincil ✗İkincilYok ✓
Cache perf.Kötü ✗En iyi ✓İyiOrta
SilmeBasit ✓TombstoneTombstoneTombstone
α > 1.0Çalışır ✓İmkansız ✗İmkansız ✗İmkansız ✗
BellekPointer ek yüküMin ✓Min ✓Min ✓
Rehash eşiği0.75 – 1.00.5 – 0.750.50.6 – 0.75
Hash sayısı1112
Koda karmaşıklıkOrtaBasit ✓OrtaKarmaşık
KullanımJava HashMap
Go map
Python dict
Rust HashMap
Ders kitaplarıTeorik ideal

Kümeleme Türleri — Görsel Karşılaştırma

Birincil Kümeleme (Linear)

Ardışık dolu hücreler büyüyen bir küme oluşturur. Herhangi bir hash değeri kümenin içine veya hemen bitişiğine düşerse, kümenin sonuna eklenir → küme büyür.
Etki: Büyük kümeler diğer kümelerle birleşir → felaket.

İkincil Kümeleme (Quadratic)

Aynı hash değerine sahip anahtarlar aynı probe dizisini izler (h, h+1, h+4, h+9...). Farklı hash değerleri farklı diziler üretir.
Etki: Birincilden hafif ama yine verimsiz.

Kümeleme Yok (Double Hash)

Her anahtar kendine özgü adım boyutuna sahip. Aynı h₁ bile farklı h₂ üretir → probe dizileri tamamen bağımsız.
Etki: Uniform probing'e en yakın gerçek yöntem.

Modern Çarpışma Çözüm Teknikleri

TeknikAçıklamaKullanan
Robin Hood Hashing"Zenginden al, fakire ver" — ekleme sırasında probe sayısı fazla olan elemanlar, az olan elemanlarla yer değiştirir. Probe sayıları dengelenir → en kötü durum iyileşir.Rust HashMap
Cuckoo Hashingİki ayrı hash fonksiyonu, iki ayrı tablo. Çarpışmada mevcut elemanı diğer tabloya taşı. Arama garantili O(1) — en fazla 2 bakış.Hardware hash, ağ paketleri
Hopscotch HashingHer kova bir "komşuluk" (neighborhood) bölgesi tutar. Elemanlar kendi bölgelerinden uzaklaşamaz → cache-friendly + iyi dağılım.Eşzamanlı (concurrent) tablolar
Swiss TableSIMD komutları ile 16 kovayı aynı anda kontrol eder. Metadata (hash'in üst 7 biti) ayrı dizide tutulur → çok hızlı probe.Google Abseil, C++ absl::flat_hash_map
Chaining → TreeZincir 8 elemandan uzunsa bağlı listeyi kırmızı-siyah ağaca dönüştür → en kötü O(n) → O(log n).Java 8+ HashMap

Demo — 3 Yöntem Yan Yana

Aynı 6 anahtarı 3 farklı yöntemle ekleyip probe sayılarını karşılaştıralım. Tablo boyutu = 11, hash(key) = key % 11.

karşılaştırmalı demo — çıktı
Eklenecek anahtarlar: 22, 1, 13, 11, 24, 33
Tablo boyutu: 11

══════ Chaining (Zincirleme) ══════
  insert(22): idx=0, zincir uzunluğu 1      ← 0 çarpışma
  insert(1):  idx=1, zincir uzunluğu 1
  insert(13): idx=2, zincir uzunluğu 1
  insert(11): idx=0, zincir uzunluğu 2      ← çarpışma! 22 ile aynı kova
  insert(24): idx=2, zincir uzunluğu 2      ← çarpışma! 13 ile aynı kova
  insert(33): idx=0, zincir uzunluğu 3      ← çarpışma! 22,11 ile aynı kova

  Tablo durumu:
    [0]: 33 → 11 → 22 → ∅   (zincir: 3)
    [1]: 1 → ∅               (zincir: 1)
    [2]: 24 → 13 → ∅         (zincir: 2)
    [3-10]: ∅
  Toplam probe: 6 (her ekleme 1)

══════ Linear Probing ══════
  insert(22): pos=0                          ← 1 probe
  insert(1):  pos=1                          ← 1 probe
  insert(13): pos=2                          ← 1 probe
  insert(11): 0(dolu)→1(dolu)→2(dolu)→pos=3 ← 4 probe! küme
  insert(24): 2(dolu)→3(dolu)→pos=4          ← 3 probe
  insert(33): 0,1,2,3,4(dolu)→pos=5          ← 6 probe! küme büyüdü

  Tablo: [22][1][13][11][24][33][ ][ ][ ][ ][ ]
         ← ──────── küme ──────── →
  Toplam probe: 16                           ← kümeleme etkisi belirgin

══════ Double Hashing (h₂ = 7 - key%7) ══════
  insert(22): pos=0                          ← 1 probe
  insert(1):  pos=1                          ← 1 probe
  insert(13): pos=2                          ← 1 probe
  insert(11): h₁=0,h₂=3 → 0(dolu)→pos=3    ← 2 probe
  insert(24): h₁=2,h₂=4 → 2(dolu)→pos=6    ← 2 probe
  insert(33): h₁=0,h₂=2 → 0(dolu)→pos=2    ← 2(dolu)→pos=4
              0,2(dolu)→pos=4                ← 3 probe

  Tablo: [22][1][13][11][33][ ][24][ ][ ][ ][ ]
          ↑     ↑   ↑   ↑      ↑   ← dağınık, küme yok!
  Toplam probe: 10                           ← linear'dan %38 daha az

══════ Sonuç ══════
  Chaining :  6 probe  (her ekleme O(1), ama zincir uzar)
  Linear   : 16 probe  (kümeleme → feci)
  Double   : 10 probe  (dengeli dağılım ✓)

Hangi Yöntemi Seçmeliyim?

Chaining Seç — Eğer:

• Load factor yüksek olabilir (α > 0.75)
• Silme işlemi sık yapılacak
• Anahtar boyutu büyük (pointer daha ucuz)
• Basit implementasyon istiyorsan
• Eşzamanlı erişim (concurrent) gerekiyorsa

Open Addressing Seç — Eğer:

• Bellek kısıtlı (pointer ek yükü istemiyorsun)
• Cache performansı kritik
• α düşük tutulabilir (< 0.7)
• Silme nadir yapılıyor
• Küçük, sabit boyutlu veriler
Karar Ağacı — Çarpışma Çözüm Yöntemi Seçimi
① Bellek kısıtlı mı?
  → Evet → Open Addressing
  → Hayır → ②

② Silme sık mı?
  → Evet → Chaining (tombstone yok)
  → Hayır → ③

③ Cache performansı kritik mi?
  → Evet, α düşük → Linear Probing
  → Evet, α orta → Robin Hood / Swiss Table
  → Hayır → ④

④ Garantili O(1) arama gerekli mi?
  → Evet → Cuckoo Hashing
  → Hayır → Double Hashing (en iyi genel amaçlı)

Sonuç

Hash tablolarında çarpışma kaçınılmazdır — Birthday Paradox bile küçük tablolarda yüksek çarpışma olasılığı gösterir. Çarpışma çözümünün kalitesi doğrudan hash tablosunun performansını belirler.

Zincirleme (Chaining) en basit yöntemdir: her kovada bağlı liste tutulur, α > 1.0'da bile çalışır, silme doğaldır. Ancak pointer takibi cache-unfriendly'dir ve ek bellek gerektirir.

Open Addressing ailesi tüm elemanları dizi içinde saklar — bağlı liste yoktur. Linear Probing cache-friendly ama birincil kümeleme sorunu yaşar; Quadratic Probing kümelemeyi azaltır ama tüm kovaları ziyaret edemeyebilir; Double Hashing her anahtara özgü adım boyutuyla hem birincil hem ikincil kümelemeyi ortadan kaldırır.

Modern implementasyonlar (Robin Hood, Cuckoo, Swiss Table) bu temel yöntemlerin zayıflıklarını CPU mimarisi bilgisiyle aşar. Rust'ın Robin Hood'u dengeleme yapar, Java 8 zincir uzarsa ağaca geçer, Google'ın Swiss Table'ı SIMD ile 16 kovayı paralel kontrol eder.

Bölüm 7.2 özeti: Çarpışma = farklı anahtarlar → aynı indeks. Chaining: her kovada bağlı liste, α > 1 destekler, silme kolay, cache-unfriendly. Open Addressing: tüm elemanlar dizide, bağlı liste yok, bellek verimli, silmede tombstone gerekli. Linear Probing: h+i, cache-friendly ama birincil kümeleme; α=0.9'da ortalama 5.5 probe. Quadratic Probing: h+i², ikincil kümeleme; tablo boyutu asal ve α<0.5 gerekli. Double Hashing: h₁+i×h₂, kümeleme yok, en iyi dağılım; α=0.9'da 2.56 probe. Modern teknikler: Robin Hood (probe dengeleme), Cuckoo (O(1) garanti arama), Swiss Table (SIMD paralel probe), Java TreeBin (zincir→RB-Tree α=0.75).
← Veri Yapıları