Bölüm 12: Eşlemeler ve 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çeklenebilir. 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 güçlü ç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.
Klasik Soru · Performans ve Uygulamalar
Soru 21: Eşik Aşılınca Yeniden Boyutlandırma
N = 7 kovalı zincirli tabloda h(k) = k mod N; yeni düğüm zincirin başına eklenir. Her eklemeden sonra buyukluk / N > 0,75 ise sunumdaki yenidenBoyutlandir() çağrılır: yeniN = 2N + 1, eski kovalar 0'dan başlayarak gezilir, her zincir baştan sona yerlestir ile yeni diziye aktarılır.
Sırasıyla 8, 15, 22, 3, 10, 30 ekleniyor. Hangi eklemeden sonra büyütme olur? Son tabloyu ve yük faktörünü yazın.
Cevabı göster
λ sırasıyla 1/7, …, 5/7 ≈ 0,71 (eşiğin altında). 30 eklenince 6/7 ≈ 0,86 > 0,75 → 30'dan sonra büyütme; yeniN = 2·7 + 1 = 15.
Büyütmeden önce: kova 1: 22 → 15 → 8, kova 2: 30, kova 3: 10 → 3. Aktarma sırası: 22, 15, 8, 30, 10, 3 (k mod 15).
| Kova | 0 | 3 | 7 | 8 | 10 |
|---|---|---|---|---|---|
| Zincir | 30 → 15 | 3 | 22 | 8 | 10 |
Diğer kovalar boş; λ = 6/15 = 0,4. Aynı zincirdeki 8, 15, 22 ayrı kovalara dağıldı; 15 ile 30 (ikisi de 0 mod 15) bu kez çakıştı. Eski indisler geçersiz olduğu için her anahtar yeniden hash'lenir: bu ekleme O(n), ama ekleme başına amortize O(1).
Klasik Soru · Açık Adresleme
Soru 22: Çift Hash ile Yerleştirme
N = 11, h(k) = k mod 11, h₂(k) = 7 − (k mod 7) ve j = (h + i · h₂) mod 11 (i = 0, 1, 2, …) ile boş tabloya sırasıyla 49, 54, 33, 93, 59, 60 ekleniyor. Her anahtarın denediği yuvaları ve son tabloyu yazın.
Cevabı göster
| k | h | h₂ | Denenen yuvalar |
|---|---|---|---|
| 49 | 5 | 7 | 5 |
| 54 | 10 | 2 | 10 |
| 33 | 0 | 2 | 0 |
| 93 | 5 | 5 | 5, 10, (5 + 10) mod 11 = 4 |
| 59 | 4 | 4 | 4, 8 |
| 60 | 5 | 3 | 5, 8, (5 + 6) mod 11 = 0, 3 |
Tablo: 0: 33, 3: 60, 4: 93, 5: 49, 8: 59, 10: 54 (diğerleri boş).
93 ile 60 aynı ev yuvasına (5) düşer ama adımları farklıdır (5 ve 3): biri 10'a, öbürü 8'e sıçrar; doğrusal ve karesel yoklamadaki gibi aynı diziyi izlemezler. Adım 1…7 ile N = 11 aralarında asal olduğundan yoklama dizisi tüm yuvaları dolaşabilir.
Çoktan Seçmeli · Performans ve Uygulamalar
Soru 23: HashMap Kova İndisi
131079 = 0x20007 = 2 · 65536 + 7 olduğuna göre aşağıdaki satır ne yazdırır?
static int indis(Object key, int n) { int h = key.hashCode(); int m = h ^ (h >>> 16); return m & (n - 1); // n = 2^k } System.out.println(indis(131079, 16));
Doğru: A. Integer'ın hash kodu kendisidir: h = 0x20007, h >>> 16 = 2. Alt 4 bit: 0111 ^ 0010 = 0101 = 5. B, karıştırmayı atlamaktır: 131079 & 15 = 7; üst bitler indise hiç katılmaz. C, XOR yerine toplamaktır: 7 + 2 = 9. D, maske olarak & n (10000₂) kullanmaktır: yalnızca tek bir bit görülür. n 2'nin kuvveti olduğundan & (n − 1), alt k biti alır ve sonucu 0…n − 1 aralığında tutar.
Çoktan Seçmeli · Açık Adresleme
Soru 24: Birikmiş Silindi İşaretleri
Doğrusal yoklamalı tabloda (N = 7) çok sayıda ekleme ve silmeden sonra durum [✗, 8, ✗, ✗, 11, ✗, ✗] (✗ = SILINDI); yalnızca 2 canlı anahtar var, λ = 2/7. Sunumdaki yuvaBul(19) kaç yuvaya bakar ve ne döndürür?
Doğru: C. h = 19 mod 7 = 5; döngü yalnızca null'da durur. 5, 6, 0, 2, 3 ✗; 8 ve 11 eşleşmez: 7 yoklama. İlk ✗ yuva 5 olduğundan uygun = 5, dönüş −(5 + 1) = −6. A, ✗'yi boş sanmaktır: arama erken biter, ötedeki anahtar kaçabilir. B yanlış: ✗ yuvalar uygun sayılır. D, if (uygun == -1) koşulunu unutmaktır. λ küçük görünse de ✗'ler aramayı uzatır; (canlı + ✗)/N izlenir, eşik aşılınca tablo yeniden hash'lenir ve ✗'ler atılır.