Bölüm 12: Hash Tabloları

Bölüm 12: 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Ü

Ç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
  • 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 ç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
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.

Son

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