Bölüm 12: Eşlemeler ve Hash Tabloları

Bölüm 12: Eşlemeler ve Hash Tabloları · Çalışma Soruları
Eşleme ve hash fikriHash fonksiyonlarıequals ve hashCodeAyrı zincirlemeAçık adreslemeYük faktörü ve yeniden boyutlandırmaJava HashMap ve uygulamalar
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Ç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
  • put anahtar yeniyse null, varsa eski değeri döndürür; 21 için ikinci put bir güncellemedir, boyut artmaz (2).
  • remove silinen değeri döndürür; anahtar artık yoksa null.
  • 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
KovaZincir (baştan sona)
042
11 → 22
2–
331 → 24
418 → 11
533
613

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
Kova01234
Zincir20—12 → 7 → 173—

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?

0123456
0123456

10 mod 7 = 3 (ev yuvası).

012103j456

i = 0: yuva 3 boş → tablo[3] = 10 (1 yoklama).

012103456

17 mod 7 = 3: yuva 3 dolu (10), çakışma.

012103174j56

i = 1: yuva 4 boş → tablo[4] = 17 (2 yoklama).

01210317456

24 mod 7 = 3: yine yuva 3.

012103174245j6

3 (10) ve 4 (17) dolu; i = 2: yuva 5 boş → tablo[5] = 24 (3 yoklama).

0121031742456

3 mod 7 = 3: dördüncü kez yuva 3.

01210317424536j

3, 4, 5 dolu; i = 3: yuva 6 boş → tablo[6] = 3 (4 yoklama).

01210317424536

5 mod 7 = 5: ev yuvası farklı ama küme 3–6'nın içinde kalıyor.

50j1210317424536

5 (24), 6 (3) dolu; i = 2: (5 + 2) mod 7 = 0 boş → başa sarılıp tablo[0] = 5 (3 yoklama).

501210317424536

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
khDenenen yuvalar
1033
1733, 4
2433, 4, (3+4) mod 7 = 0
333, 4, 0, (3+9) mod 7 = 5
555, 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?

5012103✗424536
5012103✗424536

N = 7; 17 silinmiş, yuva 4'te ✗ var. h = 31 mod 7 = 3.

5012103j✗424536

i = 0: yuva 3 dolu, 10 ≠ 31 → sonraki yuva.

5012103✗4j24536

i = 1: yuva 4 SILINDI → ilk uygun yuva: uygun = 4.

5012103✗4j24536

Silindi ≠ boş: break yok, aramaya devam (31 ötede olabilir).

5012103✗4245j36

i = 2: yuva 5 dolu, 24 ≠ 31.

5012103✗424536j

i = 3: yuva 6 dolu, 3 ≠ 31.

50j12103✗424536

i = 4: (3 + 4) mod 7 = 0, dolu, 5 ≠ 31.

501j2103✗424536

i = 5: yuva 1 gerçekten boş. uygun zaten 4, değişmez; break: 31 bundan ötede olamaz.

5012103✗424536

31 yok: return −(4 + 1) = −5.

501210331424536

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
ia[i]ekcontainsKeygor (sonra)
033false{3=0}
18−2false{3=0, 8=1}
242false{3=0, 8=1, 4=2}
333true–

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).

Kova037810
Zincir30 → 15322810

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
khh₂Denenen yuvalar
49575
5410210
33020
93555, 10, (5 + 10) mod 11 = 4
59444, 8
60535, 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.

Son

Bölüm 12: Eşlemeler ve Hash Tabloları: Çalışma Soruları
Konu anlatımı: Eşlemeler ve Hash Tabloları sunumu
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.