📊 8.4: Hash Table Performansı

Load Factor ve Hız İlişkisi

🎯 Load Factor (Doluluk Oranı) Nedir?

Load Factor, hash table'ın ne kadar dolu olduğunu gösteren bir sayıdır.

📐 Formül

Load Factor (α) = Eleman Sayısı / Tablo Boyutu
Örnekler:
  • 10 eleman, 10 boyut → α = 10/10 = 1.0 (tam dolu)
  • 5 eleman, 10 boyut → α = 5/10 = 0.5 (yarı dolu)
  • 15 eleman, 10 boyut → α = 15/10 = 1.5 (taştı! - chaining)

🎚️ İnteraktif Load Factor Göstergesi

0.0
Boş
0.5
İdeal
0.75
Kritik
1.0
Dolu
Load Factor: 0.50
İdeal seviye! ✅

⚡ Load Factor ve Performans İlişkisi

🟢
α < 0.7
Mükemmel Performans

Az çakışma, hızlı erişim

🟡
0.7 ≤ α < 1.0
Dikkat! Yavaşlama Başlıyor

Çakışmalar artıyor

🔴
α ≥ 1.0
Kötü Performans

Çok çakışma (chaining) / Eklenemez (open addressing)

💡 Python'un Sırrı

Python dict'in load factor kritik seviyeye gelince (α ≥ 0.67) ne yapar?

→ Tabloyu 2 katına çıkarır ve tüm elemanları yeniden yerleştirir (rehashing)!

Bu sayede performans her zaman O(1) kalır.

📈 Performans Karşılaştırması

Ortalama Arama Süresi vs Load Factor

1.0
α=0.25
1.1
α=0.50
1.5
α=0.75
2.5
α=0.90
10+
α=0.99

Load factor arttıkça arama süresi katlanarak uzuyor!

🔢 Matematiksel Beklenen Değerler

Load Factor Chaining
(başarılı arama)
Chaining
(başarısız arama)
Open Addressing
(başarılı arama)
α = 0.5 1.25 1.5 1.5
α = 0.75 1.37 2.5 2.5
α = 0.9 1.45 5.5 5.5

Değerler: Ortalama deneme sayısı (probe count)

🔄 Rehashing (Yeniden Hash'leme)

Load factor kritik seviyeye gelince tablo boyutu artırılır ve tüm elemanlar yeniden yerleştirilir.

🔄 Rehashing Süreci

  1. Mevcut tablo boyutunu 2 katına çıkar (örn: 10 → 20)
  2. Yeni boş tablo oluştur
  3. Eski tablodaki her elemanı yeni hash değeri ile yeni tabloya ekle
  4. Eski tabloyu sil

⚠️ Maliyetli işlem: O(n) ama nadir olur!

Rehashing Örneği - Python
class HashTableWithRehash:
    """Otomatik rehashing yapan hash table"""

    def __init__(self, initial_size=4):
        self.size = initial_size
        self.table = [[] for _ in range(self.size)]
        self.count = 0
        self.max_load_factor = 0.75  # Kritik seviye
        self.rehash_count = 0

    def hash_function(self, key):
        return key % self.size

    def get_load_factor(self):
        return self.count / self.size

    def rehash(self):
        """Tabloyu büyüt ve yeniden yerleştir"""
        self.rehash_count += 1
        old_table = self.table
        old_size = self.size

        # Yeni tablo (2 katı)
        self.size = old_size * 2
        self.table = [[] for _ in range(self.size)]
        self.count = 0

        print(f"\n🔄 REHASHING: {old_size} → {self.size}")
        print("=" * 40)

        # Tüm elemanları yeniden ekle
        for bucket in old_table:
            for key, value in bucket:
                # Yeni hash değeri!
                self.insert(key, value, silent=True)

        print(f"✅ Rehashing tamamlandı!")
        print(f"   Yeni load factor: {self.get_load_factor():.2f}")

    def insert(self, key, value, silent=False):
        """Eleman ekle (gerekirse rehash yap)"""
        # Load factor kontrolü
        if self.get_load_factor() >= self.max_load_factor:
            if not silent:
                print(f"\n⚠️ Load factor kritik: {self.get_load_factor():.2f}")
            self.rehash()

        index = self.hash_function(key)

        # Güncelleme mi?
        for i, (k, v) in enumerate(self.table[index]):
            if k == key:
                self.table[index][i] = (key, value)
                if not silent:
                    print(f"Güncellendi: {key} → indeks {index}")
                return

        # Yeni ekleme
        self.table[index].append((key, value))
        self.count += 1

        if not silent:
            print(f"Eklendi: {key} → indeks {index} (LF: {self.get_load_factor():.2f})")

    def display_stats(self):
        print(f"\n📊 İstatistikler:")
        print(f"   Tablo boyutu: {self.size}")
        print(f"   Eleman sayısı: {self.count}")
        print(f"   Load factor: {self.get_load_factor():.2f}")
        print(f"   Rehash sayısı: {self.rehash_count}")
        print(f"   En uzun zincir: {max(len(b) for b in self.table)}")

# Test
ht = HashTableWithRehash(initial_size=4)

print("=== Hash Table ile Ekleme (Rehashing İzle) ===")
print(f"Başlangıç boyutu: 4, Max load factor: 0.75")
print()

# 10 eleman ekle - rehashing'leri gözlemle
for i in range(1, 11):
    print(f"\n--- {i}. eleman ekleniyor ---")
    ht.insert(i * 3, f"değer_{i}")

ht.display_stats()

# Son durum
print("\n=== Son Tablo Durumu ===")
for i in range(min(ht.size, 16)):  # İlk 16 indeks
    if ht.table[i]:
        keys = [k for k, v in ht.table[i]]
        print(f"[{i:2d}]: {keys}")
    else:
        print(f"[{i:2d}]: boş")
Çıktı bekleniyor...

💰 Amortized Analysis (Ortalama Maliyet)

🤔 Soru

Rehashing O(n) zaman alıyor. Peki hash table insert işlemi O(1) mi, O(n) mi?

✅ Cevap: Amortized O(1)

Çoğu insert O(1), sadece seyrek olarak rehashing oluyor:

  • 1000 insert → Belki 10 rehashing
  • Her rehashing O(n) ama toplam yine O(n)
  • Ortalama: O(n) / n = O(1)

🎯 Pratik Tavsiyeler

  • Load factor'ı 0.7 altında tut
  • Eleman sayısı belliyse, tabloyu yeterince büyük başlat
  • Python dict gibi otomatik rehashing kullan
  • Gerçek zamanlı sistemlerde rehashing süre kısıtlaması yapabilir

📋 Özet

✅ Bu Bölümde Öğrendikleriniz

  • Load Factor (α): Eleman sayısı / Tablo boyutu
  • İdeal α: 0.5 - 0.7 arası (hızlı erişim)
  • Kritik α: 0.75+ (yavaşlama başlar)
  • Rehashing: Tablo 2x büyüt, yeniden yerleştir (nadir ama O(n))
  • Amortized O(1): Ortalamada insert hala çok hızlı

📚 Sonraki Konu

Python'da hash table nasıl kullanılır? dict'in sırlarını keşfedeceğiz!