- Bir metin anahtarı için polinom hash değerini ve indeksi hesaplamak
- Tabloyu zincirleme ve doğrusal sondalamayla adım adım doldurmak
- Doluluk oranından beklenen sondalama sayısını tahmin etmek
Telefon rehberinde Elvin'in numarasını tüm listeyi tarayarak bulmak O(n), sıralı bir listede ikili aramayla bulmak O(log n) sürer. Peki adım sayısı n'ye hiç bağlı olmasaydı? Hash tablosu (özet tablosu) tam olarak bunu yapar: anahtarı doğrudan bir dizi indeksine çevirir. Python'un dict ve set türleri, veri tabanlarının hash indeksleri, önbellekler ve derleyicilerin sembol tabloları bu fikir üzerine kuruludur.
Hash fonksiyonu
m boyutlu bir dizi (hücrelerine kova denir) ve bir anahtarı 0 … m − 1 aralığında bir indekse eşleyen hash fonksiyonu h. “Anahtar → değer” çifti h(anahtar) kovasında saklanır ve arama doğrudan o kovadan başlar.
- s₀ … sₖ₋₁metnin karakterlerinin sayısal kodları
- ptaban (genellikle 31 gibi küçük bir asal sayı)
- kmetnin uzunluğu
- mkova sayısı
Polinom hash hem karakterlere hem de sıralarına bağlıdır: “ab” ile “ba” farklı hash değerleri alır.
Harflere a = 1, b = 2, c = 3 kodlarını verelim. p = 31 ve m = 11 iken “cab” anahtarı hangi kovaya düşer?
Çözümü gösterÇözümü gizle
2916 = 11 · 265 + 1 olduğundan 2916 mod 11 = 1.
“cab” 1 numaralı kovaya düşer. Uygulamada Horner yöntemi kullanılır: h = (h · 31 + kod); her karakter için bir çarpma ve bir toplama, yani O(k).
- Belirlenimcilik: aynı anahtar her zaman aynı hash değerini verir.
- Düzgün dağılım: anahtarlar kovalara yaklaşık eşit dağılır.
- Hız: hesaplama O(anahtar uzunluğu) sürer.
- Anahtarın tamamını kullanır: yalnızca ilk harfe bakan bir fonksiyon “Aysel” ile “Anar”ı aynı kovaya atar.
Çakışmalar ve çözümleri
Olası anahtarlar kovalardan çok daha fazladır; bu yüzden güvercin yuvası ilkesine göre iki farklı anahtarın aynı kovaya düşmesi, yani çakışma kaçınılmazdır. Üstelik beklenenden erken olur: buna “doğum günü paradoksu” denir. Yalnızca 23 kişilik bir grupta iki kişinin aynı gün doğmuş olma olasılığı %50'yi aşar.
- kyerleştirilen anahtar sayısı
- mkova sayısı
m = 365, k = 23: k(k − 1)/2 = 253, 253/365 ≈ 0,693, e^(−0,693) ≈ 0,5. Çakışmaların başlaması için yaklaşık √m anahtar yeterlidir.
- Zincirleme (separate chaining): her kova bir liste tutar; çakışan anahtarlar bu listeye eklenir.
- Açık adresleme: tüm anahtarlar dizinin kendisinde durur; kova doluysa bir sonraki boş yer aranır. Doğrusal sondalama: h(k), h(k) + 1, h(k) + 2, … (mod m).
- Karesel sondalama (h(k) + i²) ve çift hash (h₁(k) + i · h₂(k)), anahtarların bir yere yığılmasını (kümelenmeyi) azaltır.
m = 11, h(k) = k mod 11. 12, 44, 13, 88, 23, 94, 11, 39, 20 anahtarlarını doğrusal sondalamayla yerleştir.
Çözümü gösterÇözümü gizle
88 → 0 dolu, 1 dolu, 2 dolu → 3 (4 sondalama).
23 → 1, 2, 3 dolu → 4 (4 sondalama).
94 → 6.
11 → 0, 1, 2, 3, 4 dolu → 5 (6 sondalama).
39 → 6 dolu → 7; 20 → 9.
Sonuç: [44, 12, 13, 88, 23, 11, 94, 39, —, 20, —], α = 9/11 ≈ 0,82.
0–7 hücreleri bir küme oluşturdu: oraya düşen her yeni anahtar uzun bir yol gidecek.
def insert_linear(table, key):
m = len(table)
i = key % m
probes = 1
while table[i] is not None:
i = (i + 1) % m
probes += 1
table[i] = key
return probes
table = [None] * 11
for k in [12, 44, 13, 88, 23, 94, 11, 39, 20]:
p = insert_linear(table, k)
print(k, '-> slot', table.index(k), 'probes:', p)
print(table)
print('load factor:', round(9 / 11, 2))▸ Beklenen çıktı
12 -> slot 1 probes: 1 44 -> slot 0 probes: 1 13 -> slot 2 probes: 1 88 -> slot 3 probes: 4 23 -> slot 4 probes: 4 94 -> slot 6 probes: 1 11 -> slot 5 probes: 6 39 -> slot 7 probes: 2 20 -> slot 9 probes: 1 [44, 12, 13, 88, 23, 11, 94, 39, None, 20, None] load factor: 0.82
Doluluk oranı ve ortalama O(1)
- αdoluluk oranı (yük faktörü)
- ntablodaki anahtar sayısı
- mkova sayısı
Zincirlemede α ortalama zincir uzunluğudur ve 1'i aşabilir; açık adreslemede her zaman α < 1'dir.
- Ebaşarısız aramada (anahtar yok) beklenen sondalama sayısı
Düzgün dağılan bir hash varsayımıyla. Doğrusal sondalama formülü Knuth'un analizinden gelir; α → 1 iken sondalama sayısı hızla artar.
| α | Zincirleme: 1 + α | Doğrusal sondalama (başarısız) | Doğrusal sondalama (başarılı) ½(1 + 1/(1 − α)) |
|---|---|---|---|
| 0,5 | 1,5 | 2,5 | 1,5 |
| 0,75 | 1,75 | 8,5 | 2,5 |
| 0,9 | 1,9 | 50,5 | 5,5 |
α bir eşiği aştığında (örneğin Java HashMap'te 0,75) tablo yaklaşık iki katına büyütülür ve bütün anahtarlar yeni m için yeniden hash'lenir (rehashing). Bu ara sıra yapılan O(n) iş, dinamik dizideki gibi amortize edilir; bu yüzden ekleme, arama ve silme ortalamada O(1) kalır. En kötü durumda, yani tüm anahtarlar tek kovaya düştüğünde, O(n)'dir.
class HashMap:
def __init__(self, m=5):
self.buckets, self.n = [[] for _ in range(m)], 0
def _index(self, key):
h = 0
for ch in key:
h = h * 31 + ord(ch)
return h % len(self.buckets)
def put(self, key, value):
bucket = self.buckets[self._index(key)]
for pair in bucket:
if pair[0] == key:
pair[1] = value
return
bucket.append([key, value])
self.n += 1
if self.n / len(self.buckets) > 0.75:
pairs = [p for b in self.buckets for p in b]
self.buckets = [[] for _ in range(2 * len(self.buckets) + 1)]
for p in pairs:
self.buckets[self._index(p[0])].append(p)
def get(self, key):
for k, v in self.buckets[self._index(key)]:
if k == key:
return v
caps = HashMap()
for pair in ['Azerbaijan:Baku', 'Georgia:Tbilisi', 'Japan:Tokyo', 'Italy:Rome', 'Egypt:Cairo']:
caps.put(*pair.split(':'))
print(caps.get('Azerbaijan'), caps.get('Japan'), caps.get('France'))
print(len(caps.buckets), [len(b) for b in caps.buckets])▸ Beklenen çıktı
Baku Tokyo None 11 [1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0]
None döner.| Yapı | Arama | Ekleme | Sıra / aralık sorgusu |
|---|---|---|---|
| Sırasız dizi | O(n) | amortize O(1) | yok |
| Sıralı dizi | O(log n) | O(n) | var |
| Dengeli arama ağacı | O(log n) | O(log n) | var |
| Hash tablosu | ortalama O(1), en kötü O(n) | ortalama O(1) | yok |
Önemli noktalar
- Hash tablosu anahtarı h(k) mod m indeksine çevirir; işlemler ortalamada O(1), en kötü durumda O(n)'dir.
- Çakışmalar kaçınılmazdır (doğum günü paradoksu): yaklaşık √m anahtardan sonra başlar.
- Zincirleme kovalarda liste tutar; açık adresleme bir sonraki boş hücreyi arar ve silinen yerleri işaretler.
- Doluluk oranı α = n/m; α eşiği aşınca tablo büyür ve yeniden hash'lenir (amortize O(1)).
- Hash tablosu sıra tutmaz: aralık ve en küçük değer sorguları için ağaç seç.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.