Bölüm 14: Hash Tabloları
Çoktan Seçmeli · Hash Fikri
Soru 1: Hash Tablosunun Zayıf Yanı
Hash tablosu aşağıdaki işlemlerden hangisini ortalama O(1)'de yapamaz?
B: Hash fonksiyonu yakın anahtarları birbirinden uzak kovalara dağıtır; sıra bilgisi kaybolur. Aralık sorgusu için bütün tablo taranır: O(n + m). Sıralı gezinme ve aralık sorguları gerekiyorsa dengeli bir arama ağacı seçilir.
Çoktan Seçmeli · Hash Fonksiyonları
Soru 2: m = 64 ile Bölme
Tamsayı anahtarlar için h(k) = k mod 64 kullanılıyor. h(k) anahtarın hangi kısmına bağlıdır?
D: 64 = 26 olduğundan k mod 64, k'nin ikili gösterimindeki son 6 bittir; üstteki bitler hiç kullanılmaz. Son bitleri aynı olan örüntülü anahtarlar (ör. hep 64'ün katları) aynı kovaya düşer. Bu yüzden m için 2'nin kuvvetine yakın olmayan bir asal seçilir.
Çıktı Sorusu · Hash Fonksiyonları
Soru 3: Anagramlar ve Dizgi Hash'i
Aşağıdaki program ne yazdırır? (ASCII: a = 97, d = 100, l = 108)
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 toplama(const char *s, unsigned m) { unsigned h = 0; for (; *s; s++) h += (unsigned char)*s; return h % m; } /* main içinde */ printf("%u %u %u %u\n", dizgi_hash("dal", 13), dizgi_hash("lad", 13), toplama("dal", 13), toplama("lad", 13));
Cevabı göster
Çıktı: 12 9 6 6
"dal": 100 % 13 = 9 → (9 · 31 + 97) % 13 = 376 % 13 = 12 → (12 · 31 + 108) % 13 = 480 % 13 = 12."lad": 108 % 13 = 4 → (4 · 31 + 97) % 13 = 221 % 13 = 0 → (0 + 100) % 13 = 9.- Toplama: ikisi de 305 → 305 % 13 = 6.
Toplama karakterlerin yerini yok sayar; anagramlar hep çakışır. Polinom hash'te her karakter konumuna göre farklı bir 31'in kuvvetiyle çarpılır.
Klasik Soru · Hash Fonksiyonları
Soru 4: Katlama Yöntemi
x = 123456789 anahtarı üç haneli parçalara bölünüyor. Kaydırmalı katlama ve sınırda katlama ile elde edilen değerler nedir? Tablo boyutu m = 1000 ise ev adresleri ne olur?
Cevabı göster
- Parçalar: 123 | 456 | 789.
- Kaydırmalı katlama: parçalar olduğu gibi toplanır: 123 + 456 + 789 = 1368 → 1368 mod 1000 = 368.
- Sınırda katlama: çift sıradaki parça kâğıt katlanır gibi ters çevrilir (456 → 654): 123 + 654 + 789 = 1566 → 566.
Toplam tablo boyutunu aştığı için ayrıca mod m alınır. Katlama, anahtarın bütün hanelerini sonuca katar; yalnızca son hanelere bakan bölmeden (m = 1000) bu yönüyle iyidir.
Çoktan Seçmeli · Hash Fonksiyonları
Soru 5: Çarpma Yöntemi
Sunumdaki çarpma yöntemi (s = 2654435769 ≈ 0,618 · 232) için h_carpma(1, 4) ve h_carpma(2, 4) ne döndürür?
uint32_t h_carpma(uint32_t k, int p) { const uint32_t s = 2654435769u; return (uint32_t)(k * s) >> (32 - p); }
A: p = 4 → üstteki 4 bit alınır: 2654435769 >> 28 = 9 (2654435769 / 228 ≈ 9,9). 2 · s = 5308871538 taşar; 232'ye göre kalan 1013904242'dir ve >> 28 = 3. Sonuç 0..15 aralığındadır (18 olamaz); anahtar iki katına çıkınca hash iki katına çıkmaz.
Adım Adım Çalıştırma · Açık Adresleme
Soru 6: Karesel Yoklamayla Ekleme
M = 11 yuvalı tabloya karesel yoklamayla 14, 25, 36, 47 eklenmiş (dördünün de ev adresi 3). Şimdi ekle_karesel(58) çağrılıyor. Hangi yuvalara bakılır, 58 nereye yerleşir?
Dolu yuvalar: 1 (47), 3 (14), 4 (25), 7 (36).
58 % 11 = 3 → ev 3; d = 0: yuva 3 dolu (14).
d = 1: 3 + 1 = 4 → dolu (25).
d = 2: 3 + 4 = 7 → dolu (36).
d = 3: (3 + 9) % 11 = 1 → dolu (47).
d = 4: (3 + 16) % 11 = 8 → boş: 58 yuva 8'e, 5 yoklama. Aynı ev adresli her anahtar 3, 4, 7, 1, 8 … dizisini izler (ikincil kümelenme).
#define M 11 int ekle_karesel(int k) { int ev = k % M; for (int d = 0; d < M; d++) { int i = (ev + d * d) % M; if (tablo[i].durum != DOLU) { tablo[i].anahtar = k; tablo[i].durum = DOLU; return i; } } return -1; /* yer bulunamadı */ }
Çoktan Seçmeli · Açık Adresleme
Soru 7: Çift Hash Yoklama Dizisi
m = 13, h₁(k) = k mod 13, h₂(k) = 1 + (k mod 11) ve h(k, i) = (h₁(k) + i · h₂(k)) mod 13 ise k = 50 için ilk dört yoklama hangi yuvalara yapılır?
C: h₁ = 50 mod 13 = 11, h₂ = 1 + 50 mod 11 = 1 + 6 = 7. Yuvalar: 11, 18 mod 13 = 5, 25 mod 13 = 12, 32 mod 13 = 6. A doğrusal yoklamadır; B adım boyunu 4 alır. 13 asal olduğu için 7 adımı 13 yuvanın hepsini dolaşır.
Hata Bulma · Açık Adresleme
Soru 8: Silindi İşaretinde Duran Arama
Doğrusal yoklamalı tabloya (M = 11) sırayla 27, 16, 5, 39, 17 eklenmiş, sonra sil(16) çağrılmıştır. ara aşağıdaki gibi yazılırsa ara(39) ve ara(5) ne döndürür? Hata nedir?
int ara(int k) { int i = k % M; for (int d = 0; d < M; d++, i = (i + 1) % M) { if (tablo[i].durum != DOLU) return -1; if (tablo[i].anahtar == k) return i; } return -1; }
Cevabı göster
Tablo: 5: 27 · 6: silindi (16) · 7: 5 · 8: 39 · 9: 17.
ara(39): ev 39 % 11 = 6 → yuva SILINDI,!= DOLUdoğru → −1. Oysa 39 yuva 8'de.ara(5): ev 5 → 27, başka anahtar → yuva 6 SILINDI → −1. Oysa 5 yuva 7'de.
Arama yalnızca BOS yuvada durmalıdır; silindi işareti “burada bir zamanlar biri vardı, devam et” demektir:
if (tablo[i].durum == BOS) return -1; if (tablo[i].durum == DOLU && tablo[i].anahtar == k) return i;
Çoktan Seçmeli · Zincirleme
Soru 9: Zincirdeki Sıra
MZ = 5 kovalı boş zincirli tabloya sunumdaki zincir_ekle ile sırasıyla 3, 8, 13, 18 ekleniyor. Kova 3'ün zinciri baştan sona gezilirse hangi sıra yazılır?
D: Dört anahtarın da kovası 3'tür (k % 5). zincir_ekle yeni düğümü listenin başına koyar (O(1)); en son eklenen en öndedir. Sona ekleme sırayı korurdu ama her seferinde zincirin sonuna yürümek gerekirdi.
Adım Adım Çalıştırma · Zincirleme
Soru 10: Zincirin Sonundan Silme
Kova 3'ün zinciri 18 → 33 → 8 iken zincir_sil(8) çağrılıyor. Döngü kaç tur döner, hangi işaretçi güncellenir, bellek ne olur?
Zincir: 18 → 33 → 8 → NULL.
i = 8 % 5 = 3; p baştaki 18'i, onceki NULL'u gösterir.
18 ≠ 8 → onceki = 18, p = 33.
33 ≠ 8 → onceki = 33, p = 8.
8 == 8: döngü 2 turda biter. onceki NULL değil → 33->sonraki = p->sonraki (NULL). 8 artık zincirde değil.
free(p): düğüm geri verilir. p hâlâ eski adresi tutar (sarkan işaretçi) ama bir daha kullanılmaz.
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; if (onceki == NULL) kova[i] = p->sonraki; else onceki->sonraki = p->sonraki; free(p); return true; }
Hata Bulma · Zincirleme
Soru 11: Silmede İki Hata
Zincirden silme yordamı aşağıdaki gibi yazılmıştır. İki hatayı bulun; hangi durumda program çöker, hangi durumda tanımsız davranış oluşur? Düzeltin.
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; free(p); onceki->sonraki = p->sonraki; return true; }
Cevabı göster
- Serbest bırakılmış belleği kullanmak:
free(p)'den sonrap->sonrakiokunur. p artık sarkan işaretçidir; değer bozulmuş olabilir (tanımsız davranış). - Baştaki düğüm: aranan zincirin ilk düğümüyse döngüye hiç girilmez,
oncekiNULL kalır;onceki->sonrakiNULL göstericiyi kullanır ve program çöker. Üstelikkova[i]hiç güncellenmez.
if (p == NULL) return false; if (onceki == NULL) kova[i] = p->sonraki; else onceki->sonraki = p->sonraki; free(p); /* bağlar koptuktan sonra */ return true;
Kod Yazma · Zincirleme
Soru 12: Tabloyu Boşaltmak
Program biterken zincirli tablonun bütün düğümlerini geri veren void tablo_bosalt(void) yordamını yazın. Sonunda her kova NULL olmalı; hiçbir düğüm sızmamalı ve serbest bırakılmış bir düğüm okunmamalı.
Cevabı göster
void tablo_bosalt(void) { for (int i = 0; i < MZ; i++) { dugum *p = kova[i]; while (p != NULL) { dugum *s = p->sonraki; /* önce sakla */ free(p); p = s; } kova[i] = NULL; /* sarkan baş kalmasın */ } }
sonraki, düğüm free edilmeden önce yerel değişkende saklanır. Sayaçlı malloc/free sarmalayıcısıyla denendiğinde 4 ekleme sonrası 4 malloc, 4 free görülür. Süre O(m + n).
Çoktan Seçmeli · Yük Faktörü ve Silme
Soru 13: Beklenen Yoklama Sayısı
Doğrusal yoklamalı bir tabloda yük faktörü α = 2/3'tür. Knuth'un formülüne göre başarısız bir arama ortalama kaç yoklama sürer?
A: Başarısız arama ½(1 + 1/(1 − α)²) = ½(1 + 1/(1/3)²) = ½(1 + 9) = 5. Başarılı arama ½(1 + 1/(1 − α)) = ½(1 + 3) = 2'dir. Düzgün hash varsayımıyla başarısız arama 1/(1 − α) = 3 olurdu; aradaki fark birincil kümelenmenin bedelidir.
Adım Adım Çalıştırma · Yük Faktörü ve Silme
Soru 14: sil, ekle ve ara Birlikte
M = 11 doğrusal yoklamalı tabloda 27, 16, 5, 39, 17 var. Sırayla sil(16), ekle(28), ara(17) çağrılıyor. Yuvalar nasıl değişir, ara(17) kaç yoklama yapar?
27 ev 5; 16 ev 5 → 6; 5 ev 5 → 7; 39 ev 6 → 8; 17 ev 6 → 9.
sil önce ara(16) ile yeri bulur: ev 16 % 11 = 5 → 27, başka anahtar.
Yuva 6'da 16 bulundu (2 yoklama). Yuva BOS değil, SILINDI yapılır.
28 % 11 = 6: yuva SILINDI, yani DOLU değil → 28 oraya yazılır; silinen yer yeniden kullanıldı.
17 % 11 = 6 → 28 var, başka anahtar: devam.
Yuva 7 (5) ve 8 (39) de başka anahtar.
Yuva 9'da 17: 4 yoklama. sil(16) yuvayı BOS yapsaydı ve 28 hiç eklenmeseydi ara(17) yuva 6'da durup −1 döndürürdü.
int ekle(int k) { int i = k % M; for (int d = 0; d < M; d++, i = (i + 1) % M) if (tablo[i].durum != DOLU) { tablo[i].anahtar = k; tablo[i].durum = DOLU; return i; } return -1; } 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; return true; }
Klasik Soru · Yük Faktörü ve Silme
Soru 15: Yeniden Boyutlandırma
Doğrusal yoklamalı, m = 7 yuvalı boş tabloya (h(k) = k mod m) sırasıyla 10, 17, 24, 4 ekleniyor. Her eklemeden sonra 2 * n > m ise tablo sonraki_asal(2 * m) boyutuna büyütülüyor. Büyütme ne zaman olur? Yeni tabloda her anahtar hangi yuvadadır? Eski dizi neden olduğu gibi kopyalanamaz?
Cevabı göster
- 10 → yuva 3; 17 → 3 dolu → 4; 24 → 3, 4 dolu → 5. n = 3: 6 > 7 değil.
- 4 → ev 4; 4, 5 dolu → 6. n = 4: 8 > 7 → büyütme: sonraki_asal(14) = 17.
- Eski yuvalar 0'dan 6'ya taranır ve her anahtar yeniden eklenir: 10 → 10, 17 → 0, 24 → 7, 4 → 4. Yeni α = 4/17 ≈ 0,24.
- Ev adresi m'ye bağlıdır (k mod m). Eski indislerle kopyalansaydı 17 yuva 4'te kalır,
ara(17)yuva 0'a bakar, boş görüp “yok” derdi.
Büyütme O(n + m) sürer; boyut her seferinde iki katına çıktığı için ekleme başına amortize O(1)'dir.
Çoktan Seçmeli · Özet
Soru 16: Zincirleme mi, Açık Adresleme mi?
Öğe sayısı önceden kestirilemeyen, sık sık silme yapılan ve bellek sıkıntısı olmayan bir sözlük için hangisi en uygundur?
B: Zincirlemede α > 1 olabilir; tablo dolmaz, yalnızca yavaşlar. Silme bir bağlı liste silmesidir, silindi işareti birikmez. Açık adreslemede sık silme silindi işaretlerini çoğaltır ve aramaları uzatır. Karesel yoklamada ikincil kümelenme vardır; doğrudan adresleme anahtar evreni küçük değilse kullanılamaz.
Çoktan Seçmeli · Özet
Soru 17: Kötü Niyetli Girdi
Bir web sunucusu, isteklerdeki parametre adlarını hash tablosunda tutuyor. Saldırgan, hep aynı kovaya düşen binlerce ad göndererek işlemleri O(n)'e düşürüyor (hash flooding). Hangisi en etkili önlemdir?
C: Evrensel hash'te fonksiyon program her çalıştığında rastgele seçilir; saldırgan hangi anahtarların çakışacağını önceden hesaplayamaz ve beklenen maliyet O(1) kalır. Sabit bir fonksiyon (m asal da olsa) bilindiği sürece çakışan anahtarlar üretilebilir; yoklama yöntemini değiştirmek de çakışmayı önlemez.