Bölüm 12: Hash Tabloları
Çoktan Seçmeli · Eşleme ve Hash Fikri
Soru 1: Eşleme ADT ve Hash Tablosu
Eşleme (Map) ile hash tablosu arasındaki ilişki için aşağıdakilerden hangisi doğrudur?
C doğru: eşleme bir ADT'dir; sıralı dizi, arama ağacı ya da hash tablosuyla gerçekleştirilebilir. A ters çevrilmiştir. B yanlış: anahtarlar tekildir, aynı anahtarla put yalnızca değeri günceller. D yanlış: O(1) (ortalama) hash tablosuna özgüdür; sırasız listede get O(n)'dir.
Klasik Soru · Eşleme ve Hash Fikri
Soru 2: Map İşlemlerinin Dönüş Değerleri
Aşağıdaki kodun çıktısı nedir? put ve remove çağrılarının ne döndürdüğüne dikkat edin.
Map<Integer, String> m = new HashMap<>();
System.out.println(m.put(105, "Murat"));
m.put(21, "Leyla");
System.out.println(m.put(21, "Ali"));
System.out.println(m.get(21) + " " + m.size());
System.out.println(m.remove(105));
System.out.println(m.remove(105));
System.out.println(m.get(88) + " "
+ m.containsKey(21));Cevabı göster
Çıktı (her satır bir println):
null Leyla Ali 2 Murat null null true
putanahtar yeniysenull, varsa eski değeri döndürür; 21 için ikinciputbir güncellemedir, boyut artmaz (2).removesilinen değeri döndürür; anahtar artık yoksanull.get(88): böyle bir anahtar yok →null.
Çoktan Seçmeli · Hash Fonksiyonları
Soru 3: İyi Bir Hash Fonksiyonu
Aşağıdakilerden hangisi iyi bir hash fonksiyonundan beklenen bir özellik değildir?
D beklenemez: anahtar uzayı tablodan büyükse güvercin yuvası ilkesine göre çakışma kaçınılmazdır; amaç çakışmayı azaltmak, onu çözmek ise zincirleme/açık adreslemenin işidir. A en çekici çeldiricidir ama zorunludur: belirleyici olmayan fonksiyonla eklenen öğe bir daha bulunamaz.
Çoktan Seçmeli · Hash Fonksiyonları
Soru 4: MAD Sıkıştırma
MAD (çarp-topla-böl) yöntemi h(k) = ((a·k + b) mod p) mod N ile a = 3, b = 4, p = 17, N = 5 alınıyor. h(7) kaçtır?
B: 3·7 + 4 = 25 → 25 mod 17 = 8 → 8 mod 5 = 3. A, son mod N adımını unutmaktır (8 tabloda geçerli bir indis değil). C, mod p atlanıp 25 mod 5 alınmasıdır. D, yalnızca 7 mod 5 (bölme yöntemi) sonucudur.
Çoktan Seçmeli · Hash Fonksiyonları
Soru 5: equals/hashCode Sözleşmesi
Java'daki equals/hashCode sözleşmesi için hangisi doğrudur?
C doğru: eşit anahtarlar aynı kovaya düşmelidir. A bu kuralın tersidir ve yanlıştır: aynı hash kodu yalnızca aynı kovayı gösterir, kovada ayrıca equals ile karşılaştırılır (çakışma). B'yi istemek imkânsızdır. D tipik hatadır: kova hashCode ile seçilir; ezilmezse eşit anahtarlar farklı kovalara düşer.
Klasik Soru · Hash Fonksiyonları
Soru 6: Unutulan hashCode
Nokta sınıfı equals'ı eziyor ama hashCode'u ezmiyor. Son satır pratikte ne yazdırır, bu neden garanti değildir? Hatayı düzeltin.
class Nokta { int x, y; Nokta(int x, int y) { this.x = x; this.y = y; } public boolean equals(Object o) { return o instanceof Nokta p && x == p.x && y == p.y; } } Map<Nokta, String> m = new HashMap<>(); m.put(new Nokta(1, 2), "A"); Nokta q = new Nokta(1, 2); System.out.println(q.equals(new Nokta(1, 2)) + " " + m.get(q));
Cevabı göster
Çıktı: true null
İki nokta equals'a göre eşittir, ama hashCode ezilmediği için Object.hashCode() (nesne kimliği) kullanılır ve kodlar farklıdır. HashMap kovayı bu kodla bulur, kovada equals'tan önce hash kodlarını karşılaştırır: kodlar farklıysa equals hiç çağrılmaz → null. Garanti değildir: iki kimlik hash'i tesadüfen aynı çıkabilir.
Düzeltme: hashCode da aynı alanlarla ezilir (ya da Nokta record yapılır):
public int hashCode() { return Objects.hash(x, y); }
Adım Adım Çalıştırma · Hash Fonksiyonları
Soru 7: String Hash Kodu İzleme
Aşağıdaki kod (String.hashCode() ile aynı hesap) ne yazdırır?
hash("Ece") çağrılır. Karakter kodları: E = 69, c = 99, e = 101.
h = 0 ile başlanır.
0 < 3 → döngü sürer; s.charAt(0) = 'E' (69).
h = 31 · 0 + 69 = 69.
1 < 3 → s.charAt(1) = 'c' (99).
h = 31 · 69 + 99 = 2139 + 99 = 2238. Önceki toplam 31 ile çarpılır: konum sonucu etkiler.
2 < 3 → s.charAt(2) = 'e' (101).
h = 31 · 2238 + 101 = 69378 + 101 = 69479.
3 < 3 yanlış → döngü biter; return 69479 (= 69·31² + 99·31 + 101).
Sıkıştırma: 69479 = 13 · 5344 + 7 → floorMod(69479, 13) = 7.
Çıktı: 69479 7. "Ece" 13 kovalı tabloda kova 7'ye düşer.
int hash(String s) { int h = 0; for (int i = 0; i < s.length(); i++) h = 31 * h + s.charAt(i); return h; } // main: int kod = hash("Ece"); int indis = Math.floorMod(kod, 13); System.out.println(kod + " " + indis);
Klasik Soru · Ayrı Zincirleme
Soru 8: Ayrı Zincirleme Tablosu
N = 7 kovalı, h(k) = k mod 7 kullanan ayrı zincirlemeli tabloya sırasıyla 22, 1, 13, 11, 24, 33, 18, 42, 31 ekleniyor; yeni düğüm zincirin başına eklenir. Son tabloyu çizin ve yük faktörünü bulun.
Cevabı göster
| Kova | Zincir (baştan sona) |
|---|---|
| 0 | 42 |
| 1 | 1 → 22 |
| 2 | – |
| 3 | 31 → 24 |
| 4 | 18 → 11 |
| 5 | 33 |
| 6 | 13 |
Kovalar: 22→1, 1→1, 13→6, 11→4, 24→3, 33→5, 18→4, 42→0, 31→3. Çakışan anahtar zincirin başına geçer (22'den sonra gelen 1 başta).
λ = n / N = 9 / 7 ≈ 1,29. Zincirlemede λ > 1 olabilir; λ ortalama zincir uzunluğudur, başarısız arama ortalama λ düğüm inceler.
Çoktan Seçmeli · Ayrı Zincirleme
Soru 9: %, floorMod ve Math.abs
Aşağıdaki satır ne yazdırır?
System.out.println((-7 % 10) + " " + Math.floorMod(-7, 10) + " " + Math.abs(Integer.MIN_VALUE));
A: Java'da % sonucu bölünenin işaretini alır (−7); floorMod negatifse N ekler (3). Integer.MIN_VALUE'nun pozitif karşılığı int'e sığmaz, Math.abs onu negatif bırakır. Bu yüzden kova indisi için % N ya da Math.abs(h) % N değil, Math.floorMod(h, N) kullanılır; diğer ikisi ArrayIndexOutOfBoundsException'a yol açabilir.
Klasik Soru · Ayrı Zincirleme
Soru 10: Hash Tablosunda Çakışma
5 kovalı bir hash tablosunda h(k) = k mod 5 kullanılıyor; çakışmalar zincirle (kovanın sonuna ekleyerek) çözülüyor. 12, 7, 20, 3, 17 sırayla eklendikten sonra kovaların durumu nedir ve 17'yi aramak kaç anahtar karşılaştırması gerektirir?
Cevabı göster
| Kova | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Zincir | 20 | — | 12 → 7 → 17 | 3 | — |
12, 7 ve 17'nin üçü de kova 2'ye düşer (çakışma). 17 için h(17) = 2 ile doğrudan kova 2'ye gidilir, zincirde 12, 7, 17 denetlenir → 3 karşılaştırma.
Anahtarlar kovalara dengeli dağılırsa ekleme/arama/silme ortalama O(1); hepsi aynı kovaya düşerse zincir n uzunluğunda olur → en kötü O(n). Java'da HashMap ve HashSet bu fikirle çalışır.
Çoktan Seçmeli · Açık Adresleme
Soru 11: Açık Adresleme mi, Zincirleme mi?
Açık adresleme (open addressing) ile ayrı zincirleme karşılaştırıldığında hangisi doğrudur?
D doğru: tüm çiftler dizinin kendisinde durur (önbellek dostu), fakat λ → 1 iken yoklama sayısı patlar. A zincirleme için doğrudur; açık adreslemede yuva başına bir çift olduğundan n ≤ N, yani λ ≤ 1. B yoklama zincirini koparır, "silindi" işareti gerekir. C de yanlış: zincirlemede kovalar listedir, λ > 1 olabilir.
Adım Adım Çalıştırma · Açık Adresleme
Soru 12: Doğrusal Yoklama İzleme
Döngü bittiğinde tablo nasıl görünür ve her anahtar kaç yoklama yapar?
10 mod 7 = 3 (ev yuvası).
i = 0: yuva 3 boş → tablo[3] = 10 (1 yoklama).
17 mod 7 = 3: yuva 3 dolu (10), çakışma.
i = 1: yuva 4 boş → tablo[4] = 17 (2 yoklama).
24 mod 7 = 3: yine yuva 3.
3 (10) ve 4 (17) dolu; i = 2: yuva 5 boş → tablo[5] = 24 (3 yoklama).
3 mod 7 = 3: dördüncü kez yuva 3.
3, 4, 5 dolu; i = 3: yuva 6 boş → tablo[6] = 3 (4 yoklama).
5 mod 7 = 5: ev yuvası farklı ama küme 3–6'nın içinde kalıyor.
5 (24), 6 (3) dolu; i = 2: (5 + 2) mod 7 = 0 boş → başa sarılıp tablo[0] = 5 (3 yoklama).
Sonuç: [5, –, –, 10, 17, 24, 3]; toplam 13 yoklama. Yan yana dolu yuvalar tek bir küme oluşturdu: birincil kümelenme.
Integer[] tablo = new Integer[7]; for (int k : new int[]{10, 17, 24, 3, 5}) { int h = k % 7; // ev yuvası for (int i = 0; i < 7; i++) { int j = (h + i) % 7; if (tablo[j] == null) { tablo[j] = k; break; } // dolu: i++ } }
Klasik Soru · Açık Adresleme
Soru 13: Karesel Yoklama
Aynı anahtarlar (10, 17, 24, 3, 5) N = 7, h(k) = k mod 7 ile bu kez karesel yoklamayla ((h + i²) mod 7) ekleniyor. Son tabloyu ve her anahtarın denediği yuvaları yazın.
Cevabı göster
| k | h | Denenen yuvalar |
|---|---|---|
| 10 | 3 | 3 |
| 17 | 3 | 3, 4 |
| 24 | 3 | 3, 4, (3+4) mod 7 = 0 |
| 3 | 3 | 3, 4, 0, (3+9) mod 7 = 5 |
| 5 | 5 | 5, 6 |
Tablo: 0: 24, 3: 10, 4: 17, 5: 3, 6: 5 (1 ve 2 boş).
Sıçramalar büyüdüğü için küme dışına çabuk çıkılır (birincil kümelenme azalır). Ama aynı ev yuvasına düşen 10, 17, 24, 3 hep aynı diziyi (3, 4, 0, 5…) izler: ikincil kümelenme.
Çoktan Seçmeli · Açık Adresleme
Soru 14: İkinci Hash Fonksiyonu
Çift hash'te ikinci fonksiyon olarak neden h₂(k) = k mod 7 yerine h₂(k) = 7 − (k mod 7) kullanılır (N = 13)?
B: k 7'nin katıysa k mod 7 = 0, yoklama dizisi h, h, h, … olur ve boş yuva hiç bulunamaz. Ayrıca adım N ile aralarında asal olmalıdır; N = 13 asal olduğundan 1…7 arası her adım tüm yuvaları gezer. C yanlış: farklı anahtarlar aynı adımı alabilir (ör. 31 ve 59 → 4); çakışma azalır ama sıfırlanmaz.
Adım Adım Çalıştırma · Açık Adresleme
Soru 15: yuvaBul() ve Silindi İşareti
Tablo [5, –, –, 10, ✗, 24, 3] (N = 7, ✗ = SILINDI). yuvaBul(31) ne döndürür ve yerlestir(31) 31'i hangi yuvaya koyar?
N = 7; 17 silinmiş, yuva 4'te ✗ var. h = 31 mod 7 = 3.
i = 0: yuva 3 dolu, 10 ≠ 31 → sonraki yuva.
i = 1: yuva 4 SILINDI → ilk uygun yuva: uygun = 4.
Silindi ≠ boş: break yok, aramaya devam (31 ötede olabilir).
i = 2: yuva 5 dolu, 24 ≠ 31.
i = 3: yuva 6 dolu, 3 ≠ 31.
i = 4: (3 + 4) mod 7 = 0, dolu, 5 ≠ 31.
i = 5: yuva 1 gerçekten boş. uygun zaten 4, değişmez; break: 31 bundan ötede olamaz.
31 yok: return −(4 + 1) = −5.
yerlestir(31): j = −5 < 0 → tablo[−(j + 1)] = tablo[4] = 31. Silindi yuvası yeniden kullanıldı.
private int yuvaBul(int k) { int h = Math.floorMod(k, N), uygun = -1; for (int i = 0; i < N; i++) { int j = (h + i) % N; // doğrusal if (tablo[j] == null || tablo[j] == SILINDI) { if (uygun == -1) uygun = j; if (tablo[j] == null) break; } else if (tablo[j].anahtar == k) return j; // bulundu } return -(uygun + 1); // yok: yer kodu }
Klasik Soru · Performans ve Uygulamalar
Soru 16: Yük Faktörü ve Yoklama Sayısı
Doğrusal yoklamada başarısız bir aramanın beklenen yoklama sayısı (Knuth) ½(1 + 1/(1−λ)²)'dir. λ = 0,5 ve λ = 0,8 için bu değeri hesaplayın; sonuç açık adreslemede λ ≤ 0,5 önerisini nasıl açıklar?
Cevabı göster
- λ = 0,5: ½(1 + 1/0,25) = ½(1 + 4) = 2,5 yoklama.
- λ = 0,8: ½(1 + 1/0,04) = ½(1 + 25) = 13 yoklama.
Tablo yalnızca %50'den %80'e dolunca başarısız arama ~5 kat pahalanır; λ → 1 iken değer sonsuza gider (kümeler birleşir). Bu yüzden açık adreslemede λ ≤ 0,5 tutulur ve eşik aşılınca tablo ~2 katına büyütülür.
(Karşılaştırma: zincirlemede beklenen maliyet O(1 + λ); λ ≈ 0,75 eşiği yeterlidir.)
Çoktan Seçmeli · Performans ve Uygulamalar
Soru 17: Java HashMap İçeride
Java HashMap için aşağıdakilerden hangisi yanlıştır?
D yanlıştır (sık hata): gezinme kova sırasına göredir ve büyütmeyle değişebilir. Ekleme sırası gerekiyorsa LinkedHashMap, anahtar sırası gerekiyorsa TreeMap kullanılır. A, B, C sunumdaki HashMap ayrıntılarıdır (ağaçlaşma: zincir 8'i aşarsa ve tablo ≥ 64 kovaysa).
Çoktan Seçmeli · Performans ve Uygulamalar
Soru 18: Hash Tablosuna Uygun Olmayan İş
Aşağıdaki işlerden hangisi için hash tablosu uygun bir seçim değildir?
B: hash tablosu anahtarları dağıtır, sıra tutmaz; aralık sorgusu ve sıralı gezinme O(n) (artı sıralama) gerektirir. Bu iş için arama ağaçları uygundur. A, C, D tam anahtarla "var mı / değeri ne?" sorularıdır: ortalama O(1).
Klasik Soru · Performans ve Uygulamalar
Soru 19: ikiToplam()
ikiToplam(new int[]{3, 8, 4, 3, 6}, 6) ne döndürür? Her adımda gor eşlemesinin içeriğini izleyin; algoritmanın zaman karmaşıklığı nedir?
static int[] ikiToplam(int[] a, int hedef) { Map<Integer, Integer> gor = new HashMap<>(); for (int i = 0; i < a.length; i++) { int ek = hedef - a[i]; if (gor.containsKey(ek)) return new int[]{gor.get(ek), i}; gor.put(a[i], i); // değer -> indis } return null; }
Cevabı göster
| i | a[i] | ek | containsKey | gor (sonra) |
|---|---|---|---|---|
| 0 | 3 | 3 | false | {3=0} |
| 1 | 8 | −2 | false | {3=0, 8=1} |
| 2 | 4 | 2 | false | {3=0, 8=1, 4=2} |
| 3 | 3 | 3 | true | – |
Dönüş: [0, 3] (3 + 3 = 6). Eşleme önce kontrol edip sonra eklediği için bir öğe kendisiyle eşleşmez; aynı değerin iki kopyası doğru bulunur.
Tek geçiş, her adım ortalama O(1) → O(n) zaman, O(n) ek bellek (kaba kuvvet O(n²)).
Klasik Soru · Özet
Soru 20: Değiştirilebilir Anahtar
Bir liste HashMap'e anahtar olarak konduktan sonra değiştiriliyor. Kod ne yazdırır ve neden?
List<Integer> k = new ArrayList<>(); k.add(1); k.add(2); Map<List<Integer>, String> m = new HashMap<>(); m.put(k, "A"); k.add(3); System.out.println(m.get(k)); System.out.println(m.size());
Cevabı göster
Çıktı: null ve 1 (iki satır).
put sırasında kova, [1, 2] listesinin hash koduyla seçildi. k.add(3) sonrası listenin hashCode()'u değişti; get(k) yeni hash koduyla başka bir kovaya bakar ve bulamaz. Çift hâlâ tablodadır (size() = 1) ama erişilemez durumdadır.
Kural: equals/hashCode'da kullanılan alanlar tabloya eklendikten sonra değişmemelidir; anahtarlar için String, Integer, record gibi değişmez tipler tercih edilir.