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

Veri Yapıları
Eşleme (Map) ADTDoğrudan adreslemeHash fonksiyonuÇakışma Ayrı zincirlemeAçık adreslemeYük faktörüYeniden boyutlandırma
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Eşleme modeli

Anahtar-değer ilişkisini ve hash tablosunun temel işleyişini açıklamak.

Çakışmalar

Zincirleme ve açık adresleme yöntemlerini uygulamak.

Yük faktörü

Yük faktörü, yeniden boyutlandırma ve dağılım ilişkisini yorumlamak.

Doğruluk ve maliyet

equals/hashCode sözleşmesini uygulayıp ortalama ve en kötü durum maliyetlerini karşılaştırmak.

Eşleme ve Hash Fikri

Eşleme (Map)

  • Veri öğelerini anahtar-değer (key-value) çiftleri olarak saklar.
  • Anahtarlar her öğeyi benzersiz (tekil) olarak tanımlar; değerler bu anahtarlarla ilişkilendirilir.
  • Değere anahtar üzerinden erişilir: "Ayşe Demir'in telefonu nedir?"
  • Hash tablosuyla uygulandığında işlemler ortalama O(1) sürer.
h(anahtar)0001521-897602521-123403⋮⋮1314521-965515Ali KayaAyşe DemirCan Öz
Eşleme bir soyut veri tipidir (ne yapılacağını söyler); hash tablosu ise onu uygulayan en yaygın veri yapısıdır (nasıl yapılacağını söyler).

Eşleme ve Hash Fikri

Eşleme ADT'sinin Temel İşlevleri

İşlevjava.util.MapAçıklama
Ekleme (insertion)put(k, v)Çifti tabloya ekler
Arama (search)get(k)k anahtarının değerini döndürür; yoksa null
Silme (deletion)remove(k)k anahtarlı öğeyi çıkarır, değerini döndürür
Güncelleme (update)put(k, v′)k zaten varsa değeri v′ ile değiştirir (eskisini döndürür)
Diğersize(), isEmpty(), containsKey(k), keySet(), values()
Map<Integer, String> m = new HashMap<>();
m.put(105, "Murat");   m.put(21, "Leyla");
m.put(21, "Ali");      // güncelleme: "Leyla" → "Ali"
m.get(21);             // "Ali"
m.remove(105);         // "Murat"
m.get(88);             // null: böyle bir anahtar yok

Eşleme ve Hash Fikri

Dizi Üzerinde Arama İşlemi

  • Dizide arama işleminin zaman karmaşıklığı O(1) yapılabilir mi?
  • Öğeleri birbiriyle karşılaştırarak zor: doğrusal arama O(n), sıralı dizide ikili arama O(log n).
  • Ama aranan öğenin konumunu (indisini) biliyorsak erişim tek adımdır:
int[] siraliDizi = {1, 3, 5, 7, 9};
int aranacakIndis = 2;                   // erişilmek istenen öğenin indisi
int oge = siraliDizi[aranacakIndis];     // O(1): oge = 5
Fikir: Anahtarı doğrudan bir dizi indisine çevirebilirsek, karşılaştırma yapmadan tek adımda doğru hücreye gideriz.

Eşleme ve Hash Fikri

İndisleme: Anahtar = İndis

Anahtarlar 0–9 arasındaki tam sayılarsa, k anahtarını dizinin k numaralı hücresine koyarız. Arama tek adımdır.

Anahtar uzayı1356790112334556677899A[k] = k (anahtar = indis)

Anahtar uzayındaki her k değeri A[k] hücresine yerleştirilir; kullanılmayan hücreler boş kalır.

Anahtar uzayı1356790112334556677899A[k] = k (anahtar = indis)ara(3)A[3] dolu → bulundu ✓

ara(3): doğrudan A[3]'e bakılır → dolu → bulundu, O(1).

Anahtar uzayı1356790112334556677899A[k] = k (anahtar = indis)ara(0)A[0] boş → bulunamadı ✗

ara(0): A[0]'a bakılır → boş → bulunamadı, yine O(1).

Eşleme ve Hash Fikri

Doğrudan Adresleme ve Sınırı

0123456…2829AliBeyzaDenizOnur
  • Her olası anahtar için bir hücre ayrılır: arama, ekleme, silme O(1).
  • Anahtar uzayı büyükse bellek israfı çok büyüktür: 9 haneli okul numaraları için 109 hücre (int dizisiyle ≈ 4 GB) gerekir, oysa bölümde birkaç yüz öğrenci vardır.
  • Anahtar tam sayı değilse ("Ali", "Beyza") doğrudan indis olarak kullanılamaz.
Çözüm: Büyük anahtar uzayını küçük bir diziye sıkıştıran bir fonksiyon kullanmak → hash fonksiyonu.

Eşleme ve Hash Fikri

Hash Tekniği

  • Öğe saklama ve erişim amacıyla kullanılan bir tekniktir.
  • Öğe için hızlı bir şekilde konum (kova, bucket) bulmak için kullanılır.
  • Konum belli ise dizide öğe ekleme, çıkarma ve güncelleme kolaydır.
  • Hash tekniği bir hash fonksiyonu kullanır: aldığı anahtarı 0 … N−1 aralığında bir tam sayıya (indise) dönüştürür.
anahtar"Ali"hash fonksiyonuh(anahtar)indis8678910kovalar (buckets)

"Ali".hashCode() = 65918 → 65918 mod 13 = 8 (N = 13 kovalı tablo)

Hash Fonksiyonları

Hash Fonksiyonları

  • Hash tablosunun temelinde hash fonksiyonu yer alır: anahtarı alır, öğenin tablodaki konumunu (indisini) üretir.
Belirleyici (deterministic)Aynı anahtar her zaman aynı indisi vermeli; yoksa öğe bir daha bulunamaz.
HızlıHesaplaması O(1) olmalı (anahtar uzunluğu sabit kabul edilir).
Düzgün dağılımAnahtarları kovalara eşit yaymalı; çakışmaları azaltır.
Tüm anahtarı kullanmalıYalnızca ilk harfe bakan fonksiyon "Ali", "Ayşe", "Ahmet"i aynı kovaya atar.

Kötü fonksiyon

  • Her öğe aynı yuvaya hash'lenirse tablo bir bağlı listeye döner: arama O(n).

Kapasite (N) seçimi

  • Büyük kapasite belleği israf eder; küçük kapasite çakışmaları artırır.

Hash Fonksiyonları

Hash Kodu + Sıkıştırma

anahtar"Ali"hash kodu65918sıkıştırmamod 13indis8herhangi bir int (negatif olabilir)0 … N−1String, Integer, nesne…
Bölme yöntemih(k) = k mod N
N'yi asal seçmek, anahtarlardaki düzenli örüntülerin (10'un katları gibi) aynı kovalara yığılmasını önler.
MAD (çarp-topla-böl)h(k) = ((a·k + b) mod p) mod N
p > N asal; a, b rastgele (a ≠ 0).
kk mod 10k mod 11
10010
2009
3008
4007
5006

N = 10: hepsi aynı kovada. N = 11 (asal): hepsi farklı kovalarda.

Hash Fonksiyonları

equals ve hashCode Sözleşmesi

Eşit nesnelera.equals(b) doğruysa a.hashCode() == b.hashCode() olmak zorundadır; yoksa eşit anahtarlar farklı kovalara düşer.
Aynı hash koduİki nesnenin hash kodunun aynı olması, nesnelerin eşit olduğu anlamına gelmez. Hash tablosu kovada ayrıca equals ile karşılaştırır.
Birlikte ezmeequals yeniden tanımlanıyorsa hashCode da aynı alanlara göre yeniden tanımlanmalıdır.
Değiştirilebilir anahtarTabloya eklendikten sonra equals/hashCode alanları değişirse öğe doğru kovada aranamaz.
record Ogrenci(int no, String ad) {}
Map<Ogrenci, Double> notlar = new HashMap<>();
notlar.put(new Ogrenci(42, "Ada"), 87.5); // record: equals/hashCode hazır (Java 16+)
Hash tablosunda kova seçimini hashCode, anahtarın gerçekten aynı olup olmadığını equals belirler.

Hash Fonksiyonları · Sık hata

Sık Hata: equals Var, hashCode Yok

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;
  }
  // hashCode() yok -> Object'inki
}
Map<Nokta, String> m = new HashMap<>();
m.put(new Nokta(1, 2), "A");
m.get(new Nokta(1, 2));  // büyük olasılıkla null (!)
0123(1,2) → "A"4567

put(p1, "A"): kova, p1.hashCode() ile seçilir. Nokta ezmediği için Object.hashCode() (nesne kimliği) kullanılır; örneğin kova 3.

0123(1,2) → "A"456null → get null döner7

get(p2): p2, p1 ile eşit (equals true) ama ayrı bir nesne; kimlik hash'i (neredeyse her zaman) farklı → örneğin kova 6'ya bakılır, orası boş → null. equals hiç çağrılmaz!

01(1,2) → "A"234567

Çözüm: hashCode() da ezilir: return Objects.hash(x, y); Eşit noktalar aynı kovaya düşer, kovada equals eşleşmeyi bulur → "A".

Hash Fonksiyonları

Örnek Hash Fonksiyonları

Mod alma (bölme kalanı)
int modulusHash(int anahtar, int diziBoyu) {
  return Math.floorMod(anahtar, diziBoyu);
}

floorMod, negatif anahtarlarda da 0 … diziBoyu−1 aralığında güvenli bir indis üretir.

CRC32
long crc32Hash(String anahtar) {
  CRC32 crc = new CRC32();  // java.util.zip
  crc.update(anahtar.getBytes());
  return crc.getValue();
}
DJB2 (dize)
int djb2Hash(String s) {
  int h = 5381;
  for (int i = 0; i < s.length(); i++)
    h = (h * 33) ^ s.charAt(i);
  return h;
}

Klasik DJB2 h*33 + c kullanır; burada XOR'lu biçimi var. Sonuç negatif olabilir, indis için sıkıştırma gerekir.

Java: String.hashCode()
// Σ s[i] · 31^(n−1−i)
"abc".hashCode()  // 96354

97·31² + 98·31 + 99 = 96354

Hash Fonksiyonları · Kodu adım adım çalıştır

Kod İzleme: "Ali".hashCode()

i = 0A65i = 1l108i = 2i105kod

hash("Ali") çağrılır; her karakter Unicode koduyla (A = 65, l = 108, i = 105) işlenir.

i = 0A65i = 1l108i = 2i105kodh = 0

h = 0 ile başlanır.

i = 0A65i = 1l108i = 2i105kodh = 0

0 < 3 → döngü sürer; karakter s.charAt(0) = 'A' (65).

i = 0A65i = 1l108i = 2i105kodh = 31 · 0 + 65= 65

h = 31 · 0 + 65 = 65. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.

i = 0A65i = 1l108i = 2i105kodh = 65

1 < 3 → döngü sürer; karakter s.charAt(1) = 'l' (108).

i = 0A65i = 1l108i = 2i105kodh = 31 · 65 + 108= 2123

h = 31 · 65 + 108 = 2123. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.

i = 0A65i = 1l108i = 2i105kodh = 2123

2 < 3 → döngü sürer; karakter s.charAt(2) = 'i' (105).

i = 0A65i = 1l108i = 2i105kodh = 31 · 2123 + 105= 65918

h = 31 · 2123 + 105 = 65918. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.

i = 0A65i = 1l108i = 2i105kodh = 65918

3 < 3 yanlış → döngü biter.

i = 0A65i = 1l108i = 2i105kod65·31² + 108·31 + 105= 65918

return 65918 = 65·31² + 108·31 + 105.

i = 0A65i = 1l108i = 2i105kodkod = 65918

kod = 65918. Uzun dizelerde çarpım int sınırını aşar ve sonuç negatif olabilir.

i = 0A65i = 1l108i = 2i105kod65918 mod 13= 8

Sıkıştırma: floorMod(65918, 13) = 8 → 13 kovalı tabloda kova 8.

// String.hashCode() ile aynı hesap:
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("Ali");                // 65918
int indis = Math.floorMod(kod, 13);   // 8

Hash Fonksiyonları · Kodu adım adım çalıştır

Kod İzleme: modulusHash()

tablo0123456789

10 hücrelik boş tablo; her hücre null.

tablo0123456789

Döngü sıradaki anahtarı alır: k = 5.

tablo0123456789▲ j

modulusHash(5, 10): 5 mod 10 = 5 (bölümden kalan).

tablo01234556789▲ j

tablo[5] = 5.

tablo01234556789

Döngü sıradaki anahtarı alır: k = 1.

tablo01234556789▲ j

modulusHash(1, 10): 1 mod 10 = 1 (bölümden kalan).

tablo011234556789▲ j

tablo[1] = 1.

tablo011234556789

Döngü sıradaki anahtarı alır: k = 10.

tablo011234556789▲ j

modulusHash(10, 10): 10 mod 10 = 0 (bölümden kalan).

tablo01011234556789▲ j

tablo[0] = 10.

tablo01011234556789

Döngü sıradaki anahtarı alır: k = 26.

tablo01011234556789▲ j

modulusHash(26, 10): 26 mod 10 = 6 (bölümden kalan).

tablo0101123455626789▲ j

tablo[6] = 26.

tablo0101123455626789

Döngü sıradaki anahtarı alır: k = 99.

tablo0101123455626789▲ j

modulusHash(99, 10): 99 mod 10 = 9 (bölümden kalan).

tablo010112345562678999▲ j

tablo[9] = 99. Beş anahtar, beş farklı hücre: çakışma yok. Ama 15 gelseydi 5 ile çakışırdı.

int modulusHash(int anahtar, int diziBoyu) {
  return Math.floorMod(anahtar, diziBoyu);
}

// main:
Integer[] tablo = new Integer[10];
for (int k : new int[]{5, 1, 10, 26, 99}) {
  int j = modulusHash(k, tablo.length);
  tablo[j] = k;
}

Hash Fonksiyonları

Çakışmalar (Collisions)

  • Farklı anahtarlar aynı indisi ürettiğinde ortaya çıkar: h(5) = h(105) = 5.
  • İki öğe aynı hücrede doğrudan saklanamaz.
  • Anahtar uzayı tablodan büyük olduğu için çakışmalar kaçınılmazdır; bir çözüm stratejisi şarttır:
010, Leyla11, Tuna23455, Ali626, Ceren78999, Sena5, Ali1, Tuna10, Leyla26, Ceren99, Sena105, Murat105 % 10 = 5 → çakışma!
Ayrı zincirleme (separate chaining)Her kova, o kovaya düşen öğelerin bağlı listesini tutar.
Açık adresleme (open addressing)Dolu yuvaya gelen öğe için dizide başka boş yuva aranır.

Ayrı Zincirleme

Çakışma Çözümü: Ayrı Zincirleme

0null1null2null3null4null55"Ali"null6null7null8null9nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 5

h(5) = 5 % 10 = 5 → kova 5 boş; (5, "Ali") düğümü kovaya bağlanır.

0null11"Tuna"null2null3null4null55"Ali"null6null7null8null9nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 51 % 10 = 1

h(1) = 1 % 10 = 1 → kova 1 boş; (1, "Tuna") düğümü kovaya bağlanır.

010"Leyla"null11"Tuna"null2null3null4null55"Ali"null6null7null8null9nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 0

h(10) = 10 % 10 = 0 → kova 0 boş; (10, "Leyla") düğümü kovaya bağlanır.

010"Leyla"null11"Tuna"null2null3null4null55"Ali"null626"Ceren"null7null8null9nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 026 % 10 = 6

h(26) = 26 % 10 = 6 → kova 6 boş; (26, "Ceren") düğümü kovaya bağlanır.

010"Leyla"null11"Tuna"null2null3null4null55"Ali"null626"Ceren"null7null8null999"Sena"nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 026 % 10 = 699 % 10 = 9

h(99) = 99 % 10 = 9 → kova 9 boş; (99, "Sena") düğümü kovaya bağlanır.

010"Leyla"null11"Tuna"null2null3null4null5105"Murat"5"Ali"null626"Ceren"null7null8null999"Sena"nullHashDugumu[] kovalarh(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 026 % 10 = 699 % 10 = 9105 % 10 = 5

h(105) = 105 % 10 = 5 → kova 5 dolu (5, "Ali"): çakışma. Yeni düğüm zincirin başına eklenir.

Ayrı Zincirleme

Hash Tablosu Terminolojisi

010"Leyla"null11"Tuna"null2null3null4null5105"Murat"5"Ali"null626"Ceren"null7null8null999"Sena"nullHashDugumu[] kovalarkovalarHashDugumu[] dizisikova (bucket)dizinin bir hücresikovaSayisi = 10dizi uzunluğu = kapasitebuyukluk = 6saklanan çift sayısızinciraynı kovadaki düğümlerilkzincirin ilk düğümü↑ HashDugumuilk ↓

Ayrı Zincirleme · Java

Hash Tablosu Uygulaması: Sınıf

public class HashTablosu {
  private HashDugumu[] kovalar;   // zincirlerin ilk düğümleri
  private int kovaSayisi, buyukluk;

  public HashTablosu(int kapasite) {
    this.kovaSayisi = kapasite;
    this.kovalar = new HashDugumu[kapasite];
    this.buyukluk = 0;
  }
  private class HashDugumu {
    private Integer anahtar;
    private String deger;
    private HashDugumu sonraki;
    public HashDugumu(Integer anahtar, String deger) {
      this.anahtar = anahtar; this.deger = deger;
    }
  }
}
kovalarHer hücre bir zincirin ilk düğümünü tutar.
HashDugumuanahtar + değer + sonraki: tek yönlü bağlı liste düğümü.
new HashTablosu(10)10 kova oluşur, hepsi null; buyukluk = 0.

Ayrı Zincirleme · Java

Kova İndisi: getKovaIndisi()

// Basit yol: anahtar % kovalar.length → negatif olabilir!
private int getKovaIndisi(Integer anahtar) {
  int h = anahtar.hashCode();  // = anahtar
  return Math.floorMod(h, kovalar.length);
}
anahtaranahtar % 10Math.floorMod(anahtar, 10)Sonuç
10555kova 5
2111kova 1
4111kova 1 (21 ile çakışır)
−7−73% ile kovalar[-7] → ArrayIndexOutOfBoundsException
Java'da % işleminin sonucu bölünenin işaretini alır. Integer.hashCode() sayının kendisidir; String gibi anahtarlarda hashCode() sıkça negatif olur. Math.abs(h) % N de güvenli değildir: Math.abs(Integer.MIN_VALUE) negatiftir.

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: getKovaIndisi(41), (−7)

anahtar410123456789

getKovaIndisi(41) çağrılır; tabloda 10 kova var.

anahtar410123456789

Integer.hashCode() sayının kendisidir: h = 41.

anahtar410123456789

floorMod(41, 10) çağrılır.

anahtar410123456789▲ r

Java'da 41 % 10 = 1.

anahtar410123456789▲ r

r = 1 ≥ 0 → düzeltme gerekmez.

anahtar410123456789▲ r

return 1.

anahtar410123456789▲ r

Kova indisi 1. (21 de kova 1'e düşer: çakışma.)

anahtar-70123456789

getKovaIndisi(-7) çağrılır; tabloda 10 kova var.

anahtar-70123456789

Integer.hashCode() sayının kendisidir: h = -7.

anahtar-70123456789

floorMod(-7, 10) çağrılır.

anahtar-70123456789◄ r = -7: kovalar[-7] dizinin dışında ✗

Java'da -7 % 10 = -7: sonuç bölünenin işaretini alır.

anahtar-70123456789▲ r

r < 0 → r += 10 → r = 3. Artık 0 … 9 aralığında.

anahtar-70123456789▲ r

return 3.

anahtar-70123456789▲ r

Kova indisi 3. % kullanılsaydı kovalar[-7] → istisna.

private int getKovaIndisi(Integer anahtar) {
  int h = anahtar.hashCode();  // = anahtar
  return Math.floorMod(h, kovalar.length);
}

// floorMod'un n > 0 için eşdeğeri:
static int floorMod(int x, int n) {
  int r = x % n;       // işaret x'ten gelir
  if (r < 0) r += n;   // negatifse n ekle
  return r;
}

Ayrı Zincirleme · Java

Anahtar-Değer İkilisi Yerleştirme

public void yerlestir(Integer anahtar,
                      String deger) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  while (ilk != null) {    // anahtar var mı?
    if (ilk.anahtar.equals(anahtar)) {
      ilk.deger = deger;   // varsa güncelle
      return;
    }
    ilk = ilk.sonraki;
  }
  buyukluk++;
  HashDugumu dugum =
      new HashDugumu(anahtar, deger);
  dugum.sonraki = kovalar[kovaIndisi];
  kovalar[kovaIndisi] = dugum;   // başa ekle
}
1. Kovayı bulh(anahtar) ile O(1).
2. Zinciri taraAnahtar varsa yalnızca değer değişir; buyukluk artmaz.
3. Başa ekleYoksa yeni düğüm zincirin başına eklenir: O(1).

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: yerlestir(21), yerlestir(41)

public void yerlestir(Integer anahtar,
                      String deger) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  while (ilk != null) {    // anahtar var mı?
    if (ilk.anahtar.equals(anahtar)) {
      ilk.deger = deger;   // varsa güncelle
      return;
    }
    ilk = ilk.sonraki;
  }
  buyukluk++;
  HashDugumu dugum =
      new HashDugumu(anahtar, deger);
  dugum.sonraki = kovalar[kovaIndisi];
  kovalar[kovaIndisi] = dugum;   // başa ekle
}
01null2345105"Murat"null6789

yerlestir(21, "Leyla"): h(21) = 21 mod 10 = 1 → yalnızca kova 1'e bakılır.

01null▲ ilk2345105"Murat"null6789

Kova 1 boş: ilk = null.

01null▲ ilk2345105"Murat"null6789

ilk == null → döngü biter: 21 tabloda yok, yeni düğüm gerekir.

01null2345105"Murat"null6789

Yeni çift eklenecek: buyukluk = 2.

01null2345105"Murat"null678921"Leyla"▲ dugum

Yeni düğüm (21, "Leyla") oluşturulur; sonraki henüz null, tabloya bağlı değil.

01null2345105"Murat"null678921"Leyla"null▲ dugum

dugum.sonraki = kovalar[1] = null (kova boştu).

0121"Leyla"null2345105"Murat"null6789

kovalar[1] = dugum: yeni düğüm zincirin başında. Sona gitmeye gerek yok → O(1).

0121"Leyla"null2345105"Murat"null6789

yerlestir(41, "Sena"): h(41) = 41 mod 10 = 1 → yalnızca kova 1'e bakılır.

0121"Leyla"null▲ ilk2345105"Murat"null6789

ilk zincirin ilk düğümünü gösterir.

0121"Leyla"null▲ ilk2345105"Murat"null6789

ilk != null → döngüye girilir, bu düğüm incelenecek.

0121"Leyla"null▲ ilk2345105"Murat"null6789

21 ≠ 41 → eşleşme yok, zincirde ilerle.

0121"Leyla"null▲ ilk2345105"Murat"null6789

ilk = ilk.sonraki → null (zincir bitti).

0121"Leyla"null▲ ilk2345105"Murat"null6789

ilk == null → döngü biter: 41 tabloda yok, yeni düğüm gerekir.

0121"Leyla"null2345105"Murat"null6789

Yeni çift eklenecek: buyukluk = 3.

0121"Leyla"null2345105"Murat"null678941"Sena"▲ dugum

Yeni düğüm (41, "Sena") oluşturulur; sonraki henüz null, tabloya bağlı değil.

0121"Leyla"null2345105"Murat"null678941"Sena"▲ dugum

dugum.sonraki eski baş düğümü (21) gösterir; mevcut zincir kaybolmaz.

0141"Sena"21"Leyla"null2345105"Murat"null6789

kovalar[1] = dugum: yeni düğüm zincirin başında. Sona gitmeye gerek yok → O(1).

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: yerlestir(21, "Ali") (güncelleme)

public void yerlestir(Integer anahtar,
                      String deger) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  while (ilk != null) {    // anahtar var mı?
    if (ilk.anahtar.equals(anahtar)) {
      ilk.deger = deger;   // varsa güncelle
      return;
    }
    ilk = ilk.sonraki;
  }
  buyukluk++;
  HashDugumu dugum =
      new HashDugumu(anahtar, deger);
  dugum.sonraki = kovalar[kovaIndisi];
  kovalar[kovaIndisi] = dugum;   // başa ekle
}
0141"Sena"21"Leyla"null2345105"Murat"null6789

yerlestir(21, "Ali"): h(21) = 21 mod 10 = 1 → yalnızca kova 1'e bakılır.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

ilk zincirin ilk düğümünü gösterir.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

ilk != null → döngüye girilir, bu düğüm incelenecek.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

41 ≠ 21 → eşleşme yok, zincirde ilerle.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

ilk = ilk.sonraki → düğüm 21.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

ilk != null → döngüye girilir, bu düğüm incelenecek.

0141"Sena"21"Leyla"null▲ ilk2345105"Murat"null6789

21.equals(21) → eşit: anahtar zaten tabloda.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

Değer güncellenir: "Leyla" → "Ali". Yeni düğüm eklenmez.

0141"Sena"21"Ali"null2345105"Murat"null6789

return: buyukluk = 3 değişmez (çift sayısı aynı).

Ayrı Zincirleme · Java

Anahtar ile Değer Getirme

public String getir(Integer anahtar) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  while (ilk != null) {
    if (ilk.anahtar.equals(anahtar))
      return ilk.deger;        // bulundu
    ilk = ilk.sonraki;
  }
  return null;                 // anahtar yok
}
Yalnızca bir kovaDiğer kovalara hiç bakılmaz.
MaliyetO(1 + zincir uzunluğu). Ortalama zincir uzunluğu = n/N.
ÇağrıKovaİncelenen düğümlerDönüş
tablo.getir(105)105 % 10 = 5105"Murat"
tablo.getir(21)21 % 10 = 141, 21"Ali"
tablo.getir(88)88 % 10 = 8— (kova boş)null

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: getir(105), getir(21), getir(88)

public String getir(Integer anahtar) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  while (ilk != null) {
    if (ilk.anahtar.equals(anahtar))
      return ilk.deger;        // bulundu
    ilk = ilk.sonraki;
  }
  return null;                 // anahtar yok
}
0141"Sena"21"Ali"null2345105"Murat"null6789

getir(105): h(105) = 105 mod 10 = 5. Diğer kovalara hiç bakılmaz.

0141"Sena"21"Ali"null2345105"Murat"null▲ ilk6789

ilk kovanın ilk düğümünü gösterir.

0141"Sena"21"Ali"null2345105"Murat"null▲ ilk6789

ilk != null → döngü gövdesine girilir.

0141"Sena"21"Ali"null2345105"Murat"null▲ ilk6789

105.equals(105) → eşit.

0141"Sena"21"Ali"null2345105"Murat"null▲ ilk6789

return "Murat" — 1 karşılaştırma yetti.

0141"Sena"21"Ali"null2345105"Murat"null6789

getir(21): h(21) = 21 mod 10 = 1. Diğer kovalara hiç bakılmaz.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

ilk kovanın ilk düğümünü gösterir.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

ilk != null → döngü gövdesine girilir.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

41 ≠ 21 → aynı kovaya düşmüş başka bir anahtar (çakışma).

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

ilk = ilk.sonraki → düğüm 21.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

ilk != null → döngü gövdesine girilir.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

21.equals(21) → eşit.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

return "Ali" — 2 karşılaştırma yetti.

0141"Sena"21"Ali"null2345105"Murat"null678null9

getir(88): h(88) = 88 mod 10 = 8. Diğer kovalara hiç bakılmaz.

0141"Sena"21"Ali"null2345105"Murat"null678null▲ ilk9

Kova 8 boş: ilk = null.

0141"Sena"21"Ali"null2345105"Murat"null678null▲ ilk9

ilk == null → döngüye girilmez.

0141"Sena"21"Ali"null2345105"Murat"null678null▲ ilk9

return null: 88 tabloda yok. Boş kova → tek adımda cevap.

Ayrı Zincirleme · Java

Hash Tablosundan Anahtar Silme

public String sil(Integer anahtar) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  HashDugumu onceki = null;
  while (ilk != null &&
         !ilk.anahtar.equals(anahtar)) {
    onceki = ilk;
    ilk = ilk.sonraki;
  }
  if (ilk == null) return null;   // yok
  buyukluk--;
  if (onceki != null)       // ortadan/sondan
    onceki.sonraki = ilk.sonraki;
  else                      // baştan
    kovalar[kovaIndisi] = ilk.sonraki;
  return ilk.deger;
}
oncekiTek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.
İki durumSilinen ilk düğümse kova güncellenir; değilse onceki.sonraki.
MaliyetGetirme ile aynı: O(1 + zincir uzunluğu).

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: sil(21) (zincirin ortasından)

public String sil(Integer anahtar) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  HashDugumu onceki = null;
  while (ilk != null &&
         !ilk.anahtar.equals(anahtar)) {
    onceki = ilk;
    ilk = ilk.sonraki;
  }
  if (ilk == null) return null;   // yok
  buyukluk--;
  if (onceki != null)       // ortadan/sondan
    onceki.sonraki = ilk.sonraki;
  else                      // baştan
    kovalar[kovaIndisi] = ilk.sonraki;
  return ilk.deger;
}
0141"Sena"21"Ali"null2345105"Murat"null6789

sil(21): h(21) = 21 mod 10 = 1.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

ilk baş düğümü gösterir.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.

0141"Sena"21"Ali"null▲ ilk2345105"Murat"null6789

41 ≠ 21 → koşul doğru, döngü gövdesine girilir.

0141"Sena"21"Ali"null▲ onceki2345105"Murat"null6789

onceki = ilk → düğüm 41.

0141"Sena"21"Ali"null▲ ilk▲ onceki2345105"Murat"null6789

ilk = ilk.sonraki → düğüm 21.

0141"Sena"21"Ali"null▲ ilk▲ onceki2345105"Murat"null6789

21.equals(21) → eşit: koşul yanlış, döngü biter. Silinecek düğüm bulundu.

0141"Sena"21"Ali"null▲ ilk▲ onceki2345105"Murat"null6789

ilk != null → anahtar var, silme yapılacak.

0141"Sena"21"Ali"null▲ ilk▲ onceki2345105"Murat"null6789

buyukluk = 2.

0141"Sena"21"Ali"null▲ ilk▲ onceki2345105"Murat"null6789

onceki != null → düğüm ortada/sonda: bağı onceki üzerinden atlanır.

0141"Sena"null▲ onceki2345105"Murat"null678921"Ali"▲ ilk (koptu)

onceki.sonraki = ilk.sonraki (null): 41 artık 21'i atlar; 21 zincirden koptu.

0141"Sena"null2345105"Murat"null678921"Ali"▲ silindi

return "Ali". Kopan düğümü çöp toplayıcı temizler.

Ayrı Zincirleme · Kodu adım adım çalıştır

Kod İzleme: sil(105) (baştan), sil(88) (yok)

public String sil(Integer anahtar) {
  int kovaIndisi = getKovaIndisi(anahtar);
  HashDugumu ilk = kovalar[kovaIndisi];
  HashDugumu onceki = null;
  while (ilk != null &&
         !ilk.anahtar.equals(anahtar)) {
    onceki = ilk;
    ilk = ilk.sonraki;
  }
  if (ilk == null) return null;   // yok
  buyukluk--;
  if (onceki != null)       // ortadan/sondan
    onceki.sonraki = ilk.sonraki;
  else                      // baştan
    kovalar[kovaIndisi] = ilk.sonraki;
  return ilk.deger;
}
0141"Sena"null2345105"Murat"null6789

sil(105): h(105) = 105 mod 10 = 5.

0141"Sena"null2345105"Murat"null▲ ilk6789

ilk baş düğümü gösterir.

0141"Sena"null2345105"Murat"null▲ ilk6789

onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.

0141"Sena"null2345105"Murat"null▲ ilk6789

105.equals(105) → eşit: koşul yanlış, döngü biter. Silinecek düğüm bulundu.

0141"Sena"null2345105"Murat"null▲ ilk6789

ilk != null → anahtar var, silme yapılacak.

0141"Sena"null2345105"Murat"null▲ ilk6789

buyukluk = 1.

0141"Sena"null2345105"Murat"null▲ ilk6789

onceki == null → silinen düğüm zincirin başı: else dalı.

0141"Sena"null2345null6789105"Murat"▲ ilk (koptu)

kovalar[5] = ilk.sonraki (null): kova artık 105'i göstermez.

0141"Sena"null2345null6789105"Murat"▲ silindi

return "Murat". Kopan düğümü çöp toplayıcı temizler.

0141"Sena"null2345678null9

sil(88): h(88) = 88 mod 10 = 8.

0141"Sena"null2345678null▲ ilk9

Kova 8 boş: ilk = null.

0141"Sena"null2345678null▲ ilk9

onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.

0141"Sena"null2345678null▲ ilk9

ilk == null → döngü hemen biter.

0141"Sena"null2345678null▲ ilk9

return null: 88 yok; buyukluk değişmez.

Açık Adresleme

Açık Adresleme (Open Addressing)

  • Tüm çiftler dizinin kendisinde saklanır; yuva başına en fazla bir çift (n ≤ N).
  • Çakışmada bir yoklama dizisi (probe sequence) ile boş yuva aranır:
    h(k, i) = (h(k) + f(i)) mod N,   i = 0, 1, 2, …
Yöntemf(i)Yoklama dizisi (h(k) = 5, N = 13)Zayıf yönü
Doğrusal (linear probing)i5, 6, 7, 8, 9, …Birincil kümelenme
Karesel (quadratic probing)i²5, 6, 9, 14→1, 21→8, …İkincil kümelenme; her yuvayı denemeyebilir
Çift hash (double hashing)i · h₂(k)5, 5+d, 5+2d, … (d = h₂(k))h₂(k) ≠ 0 ve adım ile N aralarında asal olmalı
Zincirlemeye göre: ek düğüm ve referans yok, bellek ardışık (önbellek dostu). Ama: yük faktörü düşük tutulmalı ve silme özel işlem ister. N asal ise 1 … N−1 arasındaki her adım N ile aralarında asaldır.

Açık Adresleme · Kodu adım adım çalıştır

Doğrusal Yoklama (Linear Probing)

sıra:18412244593231730124134518678922101112

18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.

sıra:18412244593231730124134518678922101112▲ h

Sıradaki anahtar 44: h = 44 mod 13 = 5 (ev yuvası).

sıra:18412244593231730124134518678922101112▲ h, j

i = 0: j = (5 + 0) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518678922101112▲ h▲ j

i = 1: j = (5 + 1) mod 13 = 6 → yuva boş.

sıra:1841224459323173012413451864478922101112▲ h▲ j

tablo[6] = 44, break (2 yoklama).

sıra:1841224459323173012413451864478922101112▲ h

Sıradaki anahtar 59: h = 59 mod 13 = 7 (ev yuvası).

sıra:1841224459323173012413451864478922101112▲ h, j

i = 0: j = (7 + 0) mod 13 = 7 → yuva boş.

sıra:184122445932317301241345186447598922101112▲ h, j

tablo[7] = 59, break (1 yoklama).

sıra:184122445932317301241345186447598922101112▲ h

Sıradaki anahtar 32: h = 32 mod 13 = 6 (ev yuvası).

sıra:184122445932317301241345186447598922101112▲ h, j

i = 0: j = (6 + 0) mod 13 = 6 → yuva dolu (44), bir sonraki i.

sıra:184122445932317301241345186447598922101112▲ h▲ j

i = 1: j = (6 + 1) mod 13 = 7 → yuva dolu (59), bir sonraki i.

sıra:184122445932317301241345186447598922101112▲ h▲ j

i = 2: j = (6 + 2) mod 13 = 8 → yuva boş.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

tablo[8] = 32, break (3 yoklama).

// yerlestir(k): doğrusal yoklama, N = 13
int h = k % 13;                // ev yuvası
for (int i = 0; i < 13; i++) {
  int j = (h + i) % 13;          // h, h+1, h+2…
  if (tablo[j] == null) {      // boş mu?
    tablo[j] = k;
    break;
  }                            // dolu: i++
}

Açık Adresleme · Kodu adım adım çalıştır

Doğrusal Yoklama (devam): 31 ve 73

sıra:18412244593231730124134518644759832922101112▲ h

Sıradaki anahtar 31: h = 31 mod 13 = 5 (ev yuvası).

sıra:18412244593231730124134518644759832922101112▲ h, j

i = 0: j = (5 + 0) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

i = 1: j = (5 + 1) mod 13 = 6 → yuva dolu (44), bir sonraki i.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

i = 2: j = (5 + 2) mod 13 = 7 → yuva dolu (59), bir sonraki i.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

i = 3: j = (5 + 3) mod 13 = 8 → yuva dolu (32), bir sonraki i.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

i = 4: j = (5 + 4) mod 13 = 9 → yuva dolu (22), bir sonraki i.

sıra:18412244593231730124134518644759832922101112▲ h▲ j

i = 5: j = (5 + 5) mod 13 = 10 → yuva boş.

sıra:1841224459323173012413451864475983292210311112▲ h▲ j

tablo[10] = 31, break (6 yoklama).

sıra:1841224459323173012413451864475983292210311112▲ h

Sıradaki anahtar 73: h = 73 mod 13 = 8 (ev yuvası).

sıra:1841224459323173012413451864475983292210311112▲ h, j

i = 0: j = (8 + 0) mod 13 = 8 → yuva dolu (32), bir sonraki i.

sıra:1841224459323173012413451864475983292210311112▲ h▲ j

i = 1: j = (8 + 1) mod 13 = 9 → yuva dolu (22), bir sonraki i.

sıra:1841224459323173012413451864475983292210311112▲ h▲ j

i = 2: j = (8 + 2) mod 13 = 10 → yuva dolu (31), bir sonraki i.

sıra:1841224459323173012413451864475983292210311112▲ h▲ j

i = 3: j = (8 + 3) mod 13 = 11 → yuva boş.

sıra:184122445932317301241345186447598329221031117312▲ h▲ j

tablo[11] = 73, break (4 yoklama). 5–11 tek bir büyük küme: birincil kümelenme.

// yerlestir(k): doğrusal yoklama, N = 13
int h = k % 13;                // ev yuvası
for (int i = 0; i < 13; i++) {
  int j = (h + i) % 13;          // h, h+1, h+2…
  if (tablo[j] == null) {      // boş mu?
    tablo[j] = k;
    break;
  }                            // dolu: i++
}

Açık Adresleme · Kodu adım adım çalıştır

Karesel Yoklama (Quadratic Probing)

sıra:18412244593231730124134518678922101112

18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.

sıra:18412244593231730124134518678922101112▲ h

Sıradaki anahtar 44: h = 44 mod 13 = 5 (ev yuvası).

sıra:18412244593231730124134518678922101112▲ h, j

i = 0: j = (5 + 0²) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518678922101112▲ h▲ j

i = 1: j = (5 + 1²) mod 13 = 6 → yuva boş.

sıra:1841224459323173012413451864478922101112▲ h▲ j

tablo[6] = 44, break (2 yoklama).

sıra:1841224459323173012413451864478922101112▲ h

Sıradaki anahtar 59: h = 59 mod 13 = 7 (ev yuvası).

sıra:1841224459323173012413451864478922101112▲ h, j

i = 0: j = (7 + 0²) mod 13 = 7 → yuva boş.

sıra:184122445932317301241345186447598922101112▲ h, j

tablo[7] = 59, break (1 yoklama).

sıra:184122445932317301241345186447598922101112▲ h

Sıradaki anahtar 32: h = 32 mod 13 = 6 (ev yuvası).

sıra:184122445932317301241345186447598922101112▲ h, j

i = 0: j = (6 + 0²) mod 13 = 6 → yuva dolu (44), bir sonraki i.

sıra:184122445932317301241345186447598922101112▲ h▲ j

i = 1: j = (6 + 1²) mod 13 = 7 → yuva dolu (59), bir sonraki i.

sıra:184122445932317301241345186447598922101112▲ h▲ j

i = 2: j = (6 + 2²) mod 13 = 10 → yuva boş.

sıra:18412244593231730124134518644759892210321112▲ h▲ j

tablo[10] = 32, break (3 yoklama).

// yerlestir(k): karesel yoklama, N = 13
int h = k % 13;                // ev yuvası
for (int i = 0; i < 13; i++) {
  int j = (h + i * i) % 13;      // h, h+1, h+4…
  if (tablo[j] == null) {      // boş mu?
    tablo[j] = k;
    break;
  }                            // dolu: i++
}

Açık Adresleme · Kodu adım adım çalıştır

Karesel Yoklama (devam): 31 ve 73

sıra:18412244593231730124134518644759892210321112▲ h

Sıradaki anahtar 31: h = 31 mod 13 = 5 (ev yuvası).

sıra:18412244593231730124134518644759892210321112▲ h, j

i = 0: j = (5 + 0²) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518644759892210321112▲ h▲ j

i = 1: j = (5 + 1²) mod 13 = 6 → yuva dolu (44), bir sonraki i.

sıra:18412244593231730124134518644759892210321112▲ h▲ j

i = 2: j = (5 + 2²) mod 13 = 9 → yuva dolu (22), bir sonraki i.

sıra:18412244593231730124134518644759892210321112▲ h▲ j

i = 3: j = (5 + 3²) mod 13 = 1 → yuva boş.

sıra:1841224459323173013124134518644759892210321112▲ h▲ j

tablo[1] = 31, break (4 yoklama). Sıçramalar büyüdüğü için küme dışına çabuk çıkıldı.

sıra:1841224459323173013124134518644759892210321112▲ h

Sıradaki anahtar 73: h = 73 mod 13 = 8 (ev yuvası).

sıra:1841224459323173013124134518644759892210321112▲ h, j

i = 0: j = (8 + 0²) mod 13 = 8 → yuva boş.

sıra:184122445932317301312413451864475987392210321112▲ h, j

tablo[8] = 73, break (1 yoklama).

// yerlestir(k): karesel yoklama, N = 13
int h = k % 13;                // ev yuvası
for (int i = 0; i < 13; i++) {
  int j = (h + i * i) % 13;      // h, h+1, h+4…
  if (tablo[j] == null) {      // boş mu?
    tablo[j] = k;
    break;
  }                            // dolu: i++
}

Açık Adresleme

Çift Hash (Double Hashing)

h(k, i) = (h(k) + i · h₂(k)) mod 13,   h₂(k) = 7 − (k mod 7)  → adım 1…7 arası, asla 0 değil.

k1841224459323173
h(k) = k mod 1352957658
h₂(k) (çakışmada)———7−2 = 5——7−3 = 4—
Denenen yuvalar5295, 10765, 9, 08
03112413451863275987392210441112
44: 5 dolu → 5+5 = 10.   31: 5 dolu → 5+4 = 9 dolu → 5+8 = 13 mod 13 = 0. Aynı ev yuvasına düşen anahtarlar farklı adımlarla ilerler; kümelenme azalır.

Açık Adresleme · Kodu adım adım çalıştır

Kod İzleme: Çift Hash

sıra:18412244593231730124134518678922101112

18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.

sıra:18412244593231730124134518678922101112▲ h

h = 44 mod 13 = 5 (ev yuvası), d = 7 − 2 = 5 (adım; asla 0 olmaz).

sıra:18412244593231730124134518678922101112▲ h, j

i = 0: j = (5 + 0·5) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518678922101112▲ h▲ j

i = 1: j = (5 + 1·5) mod 13 = 10 → yuva boş.

sıra:1841224459323173012413451867892210441112▲ h▲ j

tablo[10] = 44, break (2 yoklama).

sıra:1841224459323173012413451867892210441112▲ h

h = 59 mod 13 = 7 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).

sıra:1841224459323173012413451867892210441112▲ h, j

i = 0: j = (7 + 0·4) mod 13 = 7 → yuva boş.

sıra:184122445932317301241345186759892210441112▲ h, j

tablo[7] = 59, break (1 yoklama).

sıra:184122445932317301241345186759892210441112▲ h

h = 32 mod 13 = 6 (ev yuvası), d = 7 − 4 = 3 (adım; asla 0 olmaz).

sıra:184122445932317301241345186759892210441112▲ h, j

i = 0: j = (6 + 0·3) mod 13 = 6 → yuva boş.

sıra:18412244593231730124134518632759892210441112▲ h, j

tablo[6] = 32, break (1 yoklama).

sıra:18412244593231730124134518632759892210441112▲ h

h = 31 mod 13 = 5 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).

sıra:18412244593231730124134518632759892210441112▲ h, j

i = 0: j = (5 + 0·4) mod 13 = 5 → yuva dolu (18), bir sonraki i.

sıra:18412244593231730124134518632759892210441112▲ h▲ j

i = 1: j = (5 + 1·4) mod 13 = 9 → yuva dolu (22), bir sonraki i.

sıra:18412244593231730124134518632759892210441112▲ h▲ j

i = 2: j = (5 + 2·4) mod 13 = 0 → yuva boş.

sıra:1841224459323173031124134518632759892210441112▲ h▲ j

tablo[0] = 31, break (3 yoklama).

sıra:1841224459323173031124134518632759892210441112▲ h

h = 73 mod 13 = 8 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).

sıra:1841224459323173031124134518632759892210441112▲ h, j

i = 0: j = (8 + 0·4) mod 13 = 8 → yuva boş.

sıra:184122445932317303112413451863275987392210441112▲ h, j

tablo[8] = 73, break (1 yoklama).

// yerlestir(k): çift hash, N = 13
int h = k % 13;
int d = 7 - k % 7;             // adım: 1…7
for (int i = 0; i < 13; i++) {
  int j = (h + i * d) % 13;
  if (tablo[j] == null) {      // boş mu?
    tablo[j] = k;
    break;
  }                            // dolu: i++
}

Açık Adresleme

Açık Adreslemede Silme: "Silindi" İşareti

01241345186447598329221031117312

Doğrusal yoklama sonrası tablo. 44 silinmek isteniyor.

01241345186447598329221031117312✗i=0✓i=1

sil(44): h(44) = 5 → 18 ≠ 44 → yuva 6: 44 bulundu. Yuvayı null yapsak ne olur?

012413451867598329221031117312✗i=0

Yanlış yol: yuva 6 boşaltıldı. getir(32): h(32) = 6 → boş yuva → "yok" der. Oysa 32 yuva 8'de!

01241345186✗7598329221031117312

Doğru yol: yuvaya özel bir silindi işareti (DEFUNCT, tombstone ✗) konur.

01241345186✗7598329221031117312✗i=0✗i=1✓i=2

getir(32): h = 6 → ✗ silindi, aramaya devam → 7: 59 ≠ 32 → 8: 32 bulundu.

01241345186577598329221031117312✗i=0✗i=1✗i=2✗i=3✗i=4✗i=5✗i=6✗i=7

yerlestir(57): h = 5 → 5, 6(✗), 7 … 11 dolu, 12 boş → 57 yok. İlk uygun yuva olan 6'ya yerleşir.

✗ = silindi (DEFUNCT). Arama bu yuvayı dolu gibi atlar; ekleme ise onu boş gibi yeniden kullanabilir.

Açık Adresleme

Yuva Bulma: Arama ve Eklemenin Ortak Adımı

private int yuvaBul(int k) {       // findSlot
  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
}
getir(k)j = yuvaBul(k); j ≥ 0 ise tablo[j].deger, değilse null.
yerlestir(k, v)j ≥ 0 ise güncelle; değilse tablo[−(j+1)] yuvasına yeni çift.
Diğer yoklamalarKaresel: (h + i*i) % N
Çift hash: (h + i*h2) % N

Tablo hiçbir zaman tamamen dolmaz (λ < 1 tutulur, gerekince büyütülür); bu yüzden uygun her zaman bulunur.

Açık Adresleme · Kodu adım adım çalıştır

Kod İzleme: yuvaBul(32), 44 silinmiş (✗)

01241345186✗7598329221031117312▲ h

yuvaBul(32): h = 32 mod 13 = 6; henüz uygun yuva yok (−1).

01241345186✗7598329221031117312▲ h, j

i = 0: j = 6 → yuva SILINDI (✗).

01241345186✗7598329221031117312▲ h, uygun, j

İlk boş/silindi yuva: uygun = 6 (ekleme gerekirse buraya).

01241345186✗7598329221031117312▲ h, uygun, j

Silindi ≠ boş: aramaya devam (ötesinde 32 olabilir).

01241345186✗7598329221031117312▲ h, uygun▲ j

i = 1: j = 7 → dolu (59 ≠ 32), sonraki yuva.

01241345186✗7598329221031117312▲ h, uygun▲ j

i = 2: j = 8 → dolu, tablo[8].anahtar = 32 == 32.

01241345186✗7598329221031117312▲ h, uygun▲ j

return 8 (≥ 0 = bulundu). getir tablo[8].deger döndürür; ✗ arama zincirini koparmadı.

private int yuvaBul(int k) {       // findSlot
  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
}

Açık Adresleme · Kodu adım adım çalıştır

Kod İzleme: yuvaBul(57) ve ekleme

01241345186✗7598329221031117312▲ h

yuvaBul(57): h = 57 mod 13 = 5; henüz uygun yuva yok (−1).

01241345186✗7598329221031117312▲ h, j

i = 0: j = 5 → dolu (18 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ j

i = 1: j = 6 → yuva SILINDI (✗).

01241345186✗7598329221031117312▲ h▲ uygun, j

İlk boş/silindi yuva: uygun = 6 (ekleme gerekirse buraya).

01241345186✗7598329221031117312▲ h▲ uygun, j

Silindi ≠ boş: aramaya devam (ötesinde 57 olabilir).

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 2: j = 7 → dolu (59 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 3: j = 8 → dolu (32 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 4: j = 9 → dolu (22 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 5: j = 10 → dolu (31 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 6: j = 11 → dolu (73 ≠ 57), sonraki yuva.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

i = 7: j = 12 → yuva boş.

01241345186✗7598329221031117312▲ h▲ uygun▲ j

uygun zaten 6: değişmez (ilk uygun yuva korunur).

01241345186✗7598329221031117312▲ h▲ uygun▲ j

Gerçekten boş yuva → 57 bundan ötede olamaz: break.

01241345186✗7598329221031117312▲ h▲ uygun

57 yok: return −(6 + 1) = -7 (negatif = "yok", içinde eklenecek yer).

01241345186577598329221031117312▲ uygun

yerlestir: j = -7 < 0 → tablo[−(j + 1)] = tablo[6] = 57. Silindi yuvası yeniden kullanıldı.

private int yuvaBul(int k) {       // findSlot
  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
}

Açık Adresleme

Deneyin: Açık Adresleme (N = 11)

h(k) = k mod 11. Kesikli çerçeve: yoklanan dolu yuva · turuncu: sonuç yuvası · ✗: silindi (DEFUNCT). Enter tuşu = ekle.

Performans ve Uygulamalar

Yük Faktörü (Load Factor)

  • λ = n / N  (n: çift sayısı = buyukluk, N: kova sayısı).
  • Zincirlemede λ, ortalama zincir uzunluğudur; λ > 1 olabilir ama işlemler yavaşlar.
  • Açık adreslemede λ ≤ 1 zorunludur; λ → 1 iken yoklama sayısı patlar.
  • Pratik eşikler: zincirlemede λ ≈ 0,75 (Java HashMap), açık adreslemede λ ≤ 0,5.
  • Eşik aşılınca tablo yaklaşık 2 katına büyütülür.
024681000,250,50,751yük faktörü λ1,52,52,58,55,5↑ 50,5başarısızbaşarılızincirleme 1 + λ (kesikli)

Doğrusal yoklamada beklenen yoklama sayısı (Knuth):
başarılı ½(1 + 1/(1−λ))  ·  başarısız ½(1 + 1/(1−λ)²)

Zincirlemede beklenen maliyet O(1 + λ): λ sabit tutulursa (tablo n ile birlikte büyütülürse) ekleme, arama ve silme ortalama O(1) olur.

Performans ve Uygulamalar

Yeniden Boyutlandırma (Rehashing)

0201null27123334nullN = 5, h(k) = k mod 512 → 2, 7 → 2, 20 → 0, 33 → 3λ = 4 / 5 = 0,80 > 0,75→ tabloyu büyüt

N = 5 kovalı tabloya 12, 7, 20, 33 eklendi. λ = 0,80 eşiği aştı → yeniden boyutlandırma (resizing).

0331122null3null4null5null6null778null92010nullYeni N = 11 (2·5'ten büyük ilk asal)20 mod 11 = 97 mod 11 = 712 mod 11 = 133 mod 11 = 0

Yeni dizi ayrılır ve her anahtar yeniden hash'lenir (rehashing): indisler değişir, eski konumlar geçersizdir.

0331122null3null4null5null6null778null92010nullλ = 4 / 11 ≈ 0,36çakışma kalmadımaliyet: O(n) bir kezamortize: ekleme başına O(1)

Büyütme tek seferde O(n) sürer ve pahalıdır; ama kapasite her seferinde ~2 katına çıktığından ekleme başına amortize O(1).

Performans ve Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: yenidenBoyutlandir()

private void yenidenBoyutlandir() {
  HashDugumu[] eski = kovalar;
  int yeniN = 2 * eski.length + 1;
  kovalar = new HashDugumu[yeniN];
  buyukluk = 0;  kovaSayisi = yeniN;
  for (HashDugumu ilk : eski) {   // her kova
    while (ilk != null) {
      yerlestir(ilk.anahtar, ilk.deger);
      ilk = ilk.sonraki;
    }
  }
}
eski (N = 5)020127123334

λ = 4/5 = 0,8 > 0,75 olduğu için çağrıldı. eski eski diziyi tutar.

eski (N = 5)020127123334

yeniN = 2·5 + 1 = 11: yaklaşık iki kat ve tek sayı (burada asal).

eski (N = 5)020127123334kovalar (N = 11)012345678910

kovalar artık 11 boş kovalı yeni diziyi gösterir.

eski (N = 5)020127123334kovalar (N = 11)012345678910

buyukluk = 0: yerlestir her çifti yeniden sayacak.

eski (N = 5)020127123334kovalar (N = 11)012345678910

Eski kova 0: ilk = düğüm 20.

eski (N = 5)020127123334kovalar (N = 11)01234567892010

yerlestir(20): eskiden 20 mod 5 = 0, şimdi 20 mod 11 = 9 → yeni kova 9.

eski (N = 5)020127123334kovalar (N = 11)01234567892010

ilk = null: bu zincir bitti.

eski (N = 5)020127123334kovalar (N = 11)01234567892010

Eski kova 1 boş: ilk = null, while'a girilmez.

eski (N = 5)020127123334kovalar (N = 11)01234567892010

Eski kova 2: ilk = düğüm 7.

eski (N = 5)020127123334kovalar (N = 11)012345677892010

yerlestir(7): eskiden 7 mod 5 = 2, şimdi 7 mod 11 = 7 → yeni kova 7.

eski (N = 5)020127123334kovalar (N = 11)012345677892010

ilk = ilk.sonraki → düğüm 12 (aynı eski zincir).

eski (N = 5)020127123334kovalar (N = 11)01122345677892010

yerlestir(12): eskiden 12 mod 5 = 2, şimdi 12 mod 11 = 1 → yeni kova 1.

eski (N = 5)020127123334kovalar (N = 11)01122345677892010

ilk = null: bu zincir bitti.

eski (N = 5)020127123334kovalar (N = 11)01122345677892010

Eski kova 3: ilk = düğüm 33.

eski (N = 5)020127123334kovalar (N = 11)0331122345677892010

yerlestir(33): eskiden 33 mod 5 = 3, şimdi 33 mod 11 = 0 → yeni kova 0.

eski (N = 5)020127123334kovalar (N = 11)0331122345677892010

ilk = null: bu zincir bitti.

eski (N = 5)020127123334kovalar (N = 11)0331122345677892010

Eski kova 4 boş: ilk = null, while'a girilmez.

eski (N = 5)020127123334kovalar (N = 11)0331122345677892010

Bitti: 7 ile 12 artık ayrı kovalarda. λ = 4/11 ≈ 0,36. Tüm çiftler taşındığı için O(n).

Performans ve Uygulamalar

Karmaşıklık Karşılaştırması

Eşleme uygulamasıgetiryerlestirsil
Sırasız dizi / listeO(n)O(n)*O(n)
Sıralı dizi (ikili arama)O(log n)O(n)O(n)
Hash tablosu: ortalamaO(1)O(1)O(1)
Hash tablosu: en kötüO(n)O(n)O(n)

* Anahtarın zaten var olup olmadığını kontrol etmek için tüm liste taranır.

En kötü durumTüm anahtarlar aynı kovaya düşerse tek bir uzun zincir (ya da küme) oluşur: O(n).
AlanO(n + N): kova dizisi + çiftler. Zincirlemede düğüm başına bir referans fazlası.
Sıra yokEn küçük anahtar, sıralı gezinme gibi işlemler O(n): sıralı erişim için arama ağaçları.

Performans ve Uygulamalar

Java HashMap İçeride Nasıl Çalışır?

[ ]0•123•45678•91011•12131415173349kova 1: bağlı liste(17, 33, 49 → alt 4 bit 0001)kova 9: zincir 8'i aştı (n ≥ 64) →kırmızı-siyah ağaçindis = (n − 1) & (h ^ (h >>> 16)), n = 16 = 2⁴12 öğeyi aşınca (0,75·16) → n = 32
Ayrı zincirleme, n = 2kKova sayısı hep 2'nin kuvveti (varsayılan 16); mod yerine hızlı & (n − 1) maskesi.
Bit karıştırmaMaske yalnız alt bitleri görür; h ^ (h >>> 16) üst bitleri de indise katar.
λ = 0,75 eşiğiÖğe sayısı 0,75·n'yi aşınca dizi 2 katına büyür, düğümler yeni kovalara dağıtılır.
Ağaçlaşma (Java 8+)Zincir 8 düğümü aşarsa ağaca çevrilir (n ≥ 64 ise; değilse tablo büyür): en kötü O(log n).

Performans ve Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: indis(k, 16) — karıştır, maskele

üst 16 bitalt 16 bit0123456789101112131415kesikli:maskesiz

16 kovalı (n = 2⁴) tablo. Üç anahtarın alt 16 biti aynı (…0001); yalnızca üst bitleri farklı. Maske & 15 yalnız son 4 biti görür.

üst 16 bitalt 16 bith00000000000000000000000000000001= 10123456789101112131415kesikli:maskesiz

Integer.hashCode() değerin kendisidir: h = 1 = 0x00000001. Maskesiz kova h & 15 = 1 olurdu (kesikli).

üst 16 bitalt 16 bith00000000000000000000000000000001= 1h>>>1600000000000000000000000000000000= 0m00000000000000000000000000000001= 10123456789101112131415kesikli:maskesiz

h >>> 16 = 0: üst yarı alta kayar. XOR ile m = 1; artık alt 4 bit = 0001.

üst 16 bitalt 16 bith00000000000000000000000000000001= 1h>>>1600000000000000000000000000000000= 0m00000000000000000000000000000001= 101123456789101112131415kesikli:maskesiz

m & 15 = 1 → anahtar 1 kova 1'e gider ve yazdırılır.

üst 16 bitalt 16 bith00000000000000010000000000000001= 6553701123456789101112131415kesikli:maskesiz

Integer.hashCode() değerin kendisidir: h = 65537 = 0x00010001. Maskesiz kova h & 15 = 1 olurdu (kesikli).

üst 16 bitalt 16 bith00000000000000010000000000000001= 65537h>>>1600000000000000000000000000000001= 1m00000000000000010000000000000000= 6553601123456789101112131415kesikli:maskesiz

h >>> 16 = 1: üst yarı alta kayar. XOR ile m = 65536; artık alt 4 bit = 0000.

üst 16 bitalt 16 bith00000000000000010000000000000001= 65537h>>>1600000000000000000000000000000001= 1m00000000000000010000000000000000= 655360655371123456789101112131415kesikli:maskesiz

m & 15 = 0 → anahtar 65537 kova 0'a gider ve yazdırılır.

üst 16 bitalt 16 bith00000000000000100000000000000001= 1310730655371123456789101112131415kesikli:maskesiz

Integer.hashCode() değerin kendisidir: h = 131073 = 0x00020001. Maskesiz kova h & 15 = 1 olurdu (kesikli).

üst 16 bitalt 16 bith00000000000000100000000000000001= 131073h>>>1600000000000000000000000000000010= 2m00000000000000100000000000000011= 1310750655371123456789101112131415kesikli:maskesiz

h >>> 16 = 2: üst yarı alta kayar. XOR ile m = 131075; artık alt 4 bit = 0011.

üst 16 bitalt 16 bith00000000000000100000000000000001= 131073h>>>1600000000000000000000000000000010= 2m00000000000000100000000000000011= 1310750655371123131073456789101112131415kesikli:maskesiz

m & 15 = 3 → anahtar 131073 kova 3'e gider ve yazdırılır.

üst 16 bitalt 16 bith00000000000000100000000000000001= 131073h>>>1600000000000000000000000000000010= 2m00000000000000100000000000000011= 1310750655371123131073456789101112131415kesikli:maskesiz

Karıştırmasız üçü de kova 1'de çakışırdı; karıştırmayla 1, 0, 3 kovalarına dağıldılar. Maliyet: bir kaydırma + bir XOR.

static int indis(Object key, int n) { // n = 2^k
  int h = key.hashCode();
  int m = h ^ (h >>> 16); // üst bitleri karıştır
  return m & (n - 1); // alt k bit
}
// main:
for (int k : new int[]{1, 65537, 131073})
  System.out.println(k + " -> " + indis(k, 16));

Performans ve Uygulamalar

Hash Tablolarının Kullanım Alanları

SözlükKelime → anlam; yazım denetiminde "bu kelime var mı?"
VeritabanıHash indisleri ve hash join ile anahtara göre hızlı erişim.
Bellek önbelleğiURL / sorgu → önceden hesaplanmış sonuç (ör. Redis, memcached).
Web tarayıcı geçmişiZiyaret edilen adres → son ziyaret zamanı; "daha önce açıldı mı?"
Çerez yönetimiÇerez adı → değer; oturum kimliği → kullanıcı bilgisi.
DerleyiciSembol tablosu: değişken adı → tip, adres. Java: HashMap, HashSet.

Performans ve Uygulamalar · Kodu adım adım çalıştır

Uygulama: ikiToplam([8, 3, 11, 5, 7], 12)

ahedef = 1280311125374gor (değer → indis){ } boş

Toplamı 12 olan iki indis aranıyor. Kaba kuvvet tüm çiftleri dener: O(n²). Burada bir eşleme, görülen değerleri indisleriyle tutar.

ahedef = 1280311125374▲ iek = 12 − 8 = 4gor (değer → indis){ } boş

i = 0: a[0] = 8, gereken eş ek = 4. containsKey(4) → false: 4 daha önce görülmedi.

ahedef = 1280311125374▲ igor (değer → indis)8→0

gor.put(8, 0): sonraki öğeler 8'i eş olarak arayabilsin.

ahedef = 1280311125374▲ iek = 12 − 3 = 9gor (değer → indis)8→0

i = 1: a[1] = 3, gereken eş ek = 9. containsKey(9) → false: 9 daha önce görülmedi.

ahedef = 1280311125374▲ igor (değer → indis)8→03→1

gor.put(3, 1): sonraki öğeler 3'i eş olarak arayabilsin.

ahedef = 1280311125374▲ iek = 12 − 11 = 1gor (değer → indis)8→03→1

i = 2: a[2] = 11, gereken eş ek = 1. containsKey(1) → false: 1 daha önce görülmedi.

ahedef = 1280311125374▲ igor (değer → indis)8→03→111→2

gor.put(11, 2): sonraki öğeler 11'i eş olarak arayabilsin.

ahedef = 1280311125374▲ iek = 12 − 5 = 7gor (değer → indis)8→03→111→2

i = 3: a[3] = 5, gereken eş ek = 7. containsKey(7) → false: 7 daha önce görülmedi.

ahedef = 1280311125374▲ igor (değer → indis)8→03→111→25→3

gor.put(5, 3): sonraki öğeler 5'i eş olarak arayabilsin.

ahedef = 1280311125374▲ iek = 12 − 7 = 5gor (değer → indis)8→03→111→25→3

i = 4: ek = 5, containsKey(5) true (O(1) ortalama) → a[3] + a[4] = 5 + 7 = 12. return [3, 4].

ahedef = 1280311125374▲ igor (değer → indis)8→03→111→25→3

Tek geçiş, her adımda ortalama O(1) arama + ekleme → toplam O(n) zaman, O(n) ek bellek.

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]; // gereken eş
    if (gor.containsKey(ek))
      return new int[]{gor.get(ek), i};
    gor.put(a[i], i); // değer -> indis
  }
  return null; // çift yok
}

Örnek Uygulama

Okul Numarası İçin Hash Fonksiyonu

SorunOkul numaralarını 1000 öğelik bir diziye eşit şekilde dağıtmak.
ÇözümNumaranın yapısını kullanan bir hash fonksiyonu tasarlayarak çakışmaları azaltmak.

Numara biçimi: ab070c0de  ·  örnek: 210707003

201102730475060738ab: kayıt yılıc: bölümde: sırasabit
  • ab: kayıt yılı (20–24) · c: bölüm (6 veya 7) · de: kayıt sırası (00–99, iki hane)
  • Hedef: bu biçimi 0–999 aralığında bir dizi indisine dönüştürmek.

Örnek Uygulama

Okul Numarası için Hash Fonksiyonu

hash(ab070c0de) = ((ab − 20) × 100 + (c − 6) × 500 + de) mod 1000
NumaraabcdeHesapİndis
21070600321603(1×100 + 0×500 + 3) mod 1000103
24070705024750(4×100 + 1×500 + 50) mod 1000950
23070704223742(3×100 + 1×500 + 42) mod 1000842
Yıl(ab − 20) ∈ 0…4 → 100'lük blok seçer.
Bölüm(c − 6) ∈ 0, 1 → 500 kaydırır.
Sırade ∈ 0…99 doğrudan eklenir.
5 yıl × 2 bölüm × 100 sıra = 1000 olası numara, 1000 farklı indise gider: çakışma hiç yok. Bu bir mükemmel hash (perfect hash) örneğidir; anahtar yapısı bilindiği için mümkündür.

Örnek Uygulama

calculateHash Fonksiyonu

// s: "ab070c0de" biçiminde okul numarası
public int calculateHash(String s) {
  int ab = Integer.parseInt(s.substring(0, 2));
  int c  = Integer.parseInt(s.substring(5, 6));
  int de = Integer.parseInt(s.substring(7, 9));
  int yil   = (ab - 20) * 100;   // yıl bloğu
  int bolum = (c - 6) * 500;     // bölüm: 0/500
  return (yil + bolum + de) % 1000;
}
substring(başlangıç, bitiş)bitiş dahil değildir: substring(5, 6) yalnızca 5. karakteri (c) verir; substring(7, 9) 7. ve 8. karakterleri (de).
ÖrnekcalculateHash("240707050")
ab = 24, c = 7, de = 50
400 + 500 + 50 = 950

Biçime uymayan girdi (yıl 20–24 dışı, c ∉ {6, 7}) için sonuç çakışabilir ya da negatif olabilir; gerçek kodda girdi doğrulanmalıdır.

Örnek Uygulama · Kodu adım adım çalıştır

Kod İzleme: calculateHash()

021420374057607580

calculateHash("240707050"): indisler 0 … 8.

021420374057607580ab = 24

substring(0, 2) = "24" (bitiş hariç) → ab = 24 (kayıt yılı).

021420374057607580ab = 24c = 7

substring(5, 6) = "7" → c = 7 (bölüm).

021420374057607580ab = 24c = 7de = 50

substring(7, 9) = "50" → de = 50 (kayıt sırası).

021420374057607580ab = 24c = 7de = 50

yil = (24 − 20) · 100 = 400: her yıl 100'lük bir blok.

021420374057607580ab = 24c = 7de = 50

bolum = (7 − 6) · 500 = 500: bölüm 7 ise 500 kaydırılır.

021420374057607580ab = 24c = 7de = 50

return (400 + 500 + 50) % 1000 = 950.

021120374056607083

calculateHash("210706003"): indisler 0 … 8.

021120374056607083ab = 21

substring(0, 2) = "21" (bitiş hariç) → ab = 21 (kayıt yılı).

021120374056607083ab = 21c = 6

substring(5, 6) = "6" → c = 6 (bölüm).

021120374056607083ab = 21c = 6de = 3

substring(7, 9) = "03" → de = 3 (kayıt sırası).

021120374056607083ab = 21c = 6de = 3

yil = (21 − 20) · 100 = 100: her yıl 100'lük bir blok.

021120374056607083ab = 21c = 6de = 3

bolum = (6 − 6) · 500 = 0: bölüm 6 ise kaydırma yok.

021120374056607083ab = 21c = 6de = 3

return (100 + 0 + 3) % 1000 = 103.

// s: "ab070c0de" biçiminde okul numarası
public int calculateHash(String s) {
  int ab = Integer.parseInt(s.substring(0, 2));
  int c  = Integer.parseInt(s.substring(5, 6));
  int de = Integer.parseInt(s.substring(7, 9));
  int yil   = (ab - 20) * 100;   // yıl bloğu
  int bolum = (c - 6) * 500;     // bölüm: 0/500
  return (yil + bolum + de) % 1000;
}

Özet

Özet

Eşleme (Map) ADTTekil anahtar → değer. put, get, remove; aynı anahtarla put = güncelleme.
Hash fonksiyonuhash kodu + sıkıştırma (mod N, N asal). Belirleyici, hızlı, düzgün dağılımlı.
Ayrı zincirlemeHer kova bir bağlı liste. Başa ekleme; maliyet O(1 + λ). λ > 1 olabilir.
Açık adreslemeDoğrusal (birincil kümelenme), karesel (ikincil kümelenme), çift hash. Silmede "silindi" işareti.
Yük faktörü λ = n/NEşik aşılınca (≈ 0,5–0,75) tablo ~2 katına büyütülür, tüm anahtarlar yeniden hash'lenir.
Doğruluk ve karmaşıklıkEşit anahtarların hash kodu aynı olmalıdır. Ortalama O(1), en kötü O(n); yeniden boyutlandırma amortize O(1).

Özet

Etkileşimli Simülatörler

Hash Tablosu Simülatörü →Ayrı zincirleme, adım adım hash hesabı, çakışmaların görselleştirilmesi ve otomatik yeniden boyutlandırma.
Eşleme (Map) Simülatörü →set, get, has, delete işlemleri; anahtarlar üzerinde gezinme ve ekleme sırasının izlenmesi.
Açık Adresleme Simülatörü →Doğrusal, karesel yoklama ve çift hash; yoklama dizisi adım adım, silindi işareti (✗) ve yük faktörü.

Özet

Sık Yapılan Hatalar

Math.abs(h) % NMath.abs(Integer.MIN_VALUE) negatiftir → dizi sınırı hatası. Math.floorMod(h, N) kullanın.
Yalnız equals ezmekEşit anahtarlar farklı kovaya düşer, get null döner. İkisi birlikte ezilir.
Değiştirilebilir anahtarTabloya girdikten sonra hash alanı değişen anahtar yanlış kovada kalır: bulunamaz.
Integer ile ==−128…127 dışındaki kutulu sayılar ayrı nesnelerdir; == false verir, equals kullanın.
Açık adreslemede null ile silmekYoklama zinciri kopar; "silindi" (DEFUNCT) işareti gerekir.
Sıra beklemekHashMap gezinme sırası ekleme sırası değildir: LinkedHashMap ya da TreeMap.

Özet

Örnek Problemler

  1. N = 7, h(k) = k mod 7, doğrusal yoklama: 10, 17, 24, 3, 5 sırayla eklenirse son tablo nedir?
  2. Aynı anahtarları karesel yoklama ile ekleyin.
  3. Zincirlemeli, N = 10 kovalı bir tabloda 25 çift var. λ nedir? Başarısız bir arama ortalama kaç düğüm inceler?
  4. Açık adreslemede silinen yuvayı neden doğrudan null yapamayız?
  5. 230706015 numarası için hash(ab070c0de) değerini hesaplayın.
  6. 0, 12, 24, 36 anahtarları N = 12 ve N = 11 ile k mod N kullanıldığında hangi kovalara düşer?
Cevapları göster
  1. 10→3; 17→3,4; 24→3,4,5; 3→3,4,5,6; 5→5,6,0. Tablo: 0:5, 3:10, 4:17, 5:24, 6:3.
  2. 10→3; 17→3,4; 24→3,4,(3+4=7→0); 3→3,4,0,(3+9=12→5); 5→5,6. Tablo: 0:24, 3:10, 4:17, 5:3, 6:5.
  3. λ = 25/10 = 2,5; başarısız arama ortalama λ = 2,5 düğüm inceler (tüm zincir taranır).
  4. Yoklama zinciri kopar: o yuvanın ötesine yerleşmiş anahtarlar "yok" sanılır. Çözüm: "silindi" (DEFUNCT) işareti.
  5. ab = 23, c = 6, de = 15 → (300 + 0 + 15) mod 1000 = 315.
  6. N = 12: hepsi 0 (çakışma). N = 11: 0, 1, 2, 3.

Özet

Alıştırmalar

  1. N = 11, h(k) = k mod 11, h₂(k) = 7 − (k mod 7) ile çift hash: 14, 25, 36, 3 hangi yuvalara yerleşir?
  2. N = 5 zincirli tablodaki 12, 7, 20, 33 anahtarları N = 11'e büyütülünce hangi kovalara gider?
  3. Doğrusal yoklamada λ = 0,8 iken başarısız bir arama ortalama kaç yoklama yapar?
  4. Bir kelime dizisindeki her kelimenin kaç kez geçtiğini HashMap ile sayan döngüyü yazın.
  5. hashCode() her nesne için 42 döndürürse tablo nasıl davranır?
  6. indis(32, 16) ve indis(65568, 16) nedir? Karıştırma olmasa?
Cevap ve ipuçları
  1. Denenen yuvalar: 14→3; 25→3, 6; 36→3, 9; 3→3, 7.
  2. k mod 11: 12→1, 7→7, 20→9, 33→0 (hepsi yeniden hash'lenir).
  3. ½(1 + 1/(1 − 0,8)²) = ½(1 + 25) = 13.
  4. for (String w : k) f.put(w, f.getOrDefault(w, 0) + 1); → O(n) ortalama.
  5. Hepsi tek kovaya düşer: işlemler O(n). Java 8+ o kovayı ağaca çevirir (anahtarlar Comparable ise O(log n)).
  6. 0 ve 1; karıştırmasız ikisi de 0 (çakışma).

Özet

Kaynaklar

  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
  • Oracle, Java SE 17 Map, HashMap ve HashSet API belgeleri.

Son

Bölüm 12: Eşlemeler ve Hash Tabloları
Sonraki sunum: Bölüm 13: Kümeler ve Ayrık Kümeler · Çalışma soruları · Simülatörler: Hash Table Simulator, Map Simulator, Open Addressing Simulator · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.