Bölüm 9: 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.

Maliyet analizi

Ortalama ve en kötü durum işlem 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 (indeksini) biliyorsak erişim tek adımdır:
int[] siraliDizi = {1, 3, 5, 7, 9};
int aranacakIndeks = 2;                  // erişilmek istenen öğenin indeksi
int oge = siraliDizi[aranacakIndeks];    // O(1): oge = 5
Fikir: Anahtarı doğrudan bir dizi indeksine çevirebilirsek, karşılaştırma yapmadan tek adımda doğru hücreye gideriz. Bu bölümün geri kalanı bu fikrin üzerine kuruludur.

Eşleme ve Hash Fikri

İndeksleme: Anahtar = İndeks

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 = indeks)

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 = indeks)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 = indeks)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 indeks 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 (indekse) dönüştürür.
anahtar"Ali"hash fonksiyonuh(anahtar)indeks8678910kovalar (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 (indeksini) üretir.
Belirleyici (deterministic)Aynı anahtar her zaman aynı indeksi 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 13indeks8herhangi 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ı

Örnek Hash Fonksiyonları

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

Tam sayıyı sabit bir sayıya bölüp kalanı döndürür; dizi indeksi hesaplamak için kullanışlıdır.

CRC32
long crc32Hash(String anahtar) {
  CRC32 crc = new CRC32();  // java.util.zip
  crc.update(anahtar.getBytes());
  return crc.getValue();
}
DJB2 (karakter dizisi)
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, indeks 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ı

Mod Alma Hash Fonksiyonu: Adım Adım

Anahtar uzayı012345678951102699h(k) = k % 10

Boş tablo: uzunluk = 10, indeks = h(anahtar) = anahtar % uzunluk.

Anahtar uzayı0123455678951102699h(k) = k % 105 % 10 = 5

h(5) = 5 % 10 = 5 → 5 numaralı yuvaya.

Anahtar uzayı01123455678951102699h(k) = k % 105 % 10 = 51 % 10 = 1

h(1) = 1 % 10 = 1 → 1 numaralı yuvaya.

Anahtar uzayı0101123455678951102699h(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 0

h(10) = 10 % 10 = 0 → 0 numaralı yuvaya.

Anahtar uzayı010112345562678951102699h(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 026 % 10 = 6

h(26) = 26 % 10 = 6 → 6 numaralı yuvaya.

Anahtar uzayı01011234556267899951102699h(k) = k % 105 % 10 = 51 % 10 = 110 % 10 = 026 % 10 = 699 % 10 = 9

h(99) = 99 % 10 = 9 → 9 numaralı yuvaya. Beş anahtar, beş farklı yuva: çakışma yok.

Hash Fonksiyonları

Çakışmalar (Collisions)

  • Farklı anahtarlar aynı indeksi ü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 İndeksi: getKovaIndeksi()

// Orijinal: return anahtar % kovalar.length;   → anahtar negatifse indeks de negatif!
private int getKovaIndeksi(Integer anahtar) {
  return Math.floorMod(anahtar.hashCode(), kovalar.length);  // her zaman 0 … N−1
}
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 · Java

Anahtar-Değer İkilisi Yerleştirme

public void yerlestir(Integer anahtar, String deger) {
  int kovaIndeksi = getKovaIndeksi(anahtar);
  HashDugumu ilk = kovalar[kovaIndeksi];
  while (ilk != null) {             // anahtar zaten 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[kovaIndeksi];
  kovalar[kovaIndeksi] = dugum;     // zincirin başına
}
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 · Java

Yerleştirme: Adım Adım

0null1null2null3null4null5null6null7null8null9nullHashDugumu[] kovalarkovaSayisi = 10buyukluk = 0

HashTablosu tablo = new HashTablosu(10); → 10 boş kova, buyukluk = 0.

0null1null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(105, "Murat")kovaIndeksi = 105 % 10 = 5buyukluk = 1

h(105) = 105 % 10 = 5 → kova 5 boş (ilk = null) → yeni düğüm eklenir, buyukluk = 1.

0null121"Leyla"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(21, "Leyla")kovaIndeksi = 21 % 10 = 1buyukluk = 2

h(21) = 21 % 10 = 1 → kova 1 boş → yeni düğüm eklenir, buyukluk = 2.

0null121"Leyla"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(41, "Sena")kovaIndeksi = 41 % 10 = 1ilk.anahtar = 21 ≠ 41

h(41) = 41 % 10 = 1 → kova 1 dolu: çakışma. Zincir taranır: 21 ≠ 41, sonraki = null → anahtar yok.

0null141"Sena"21"Leyla"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(41, "Sena")dugum.sonraki = kovalar[1]kovalar[1] = dugumbuyukluk = 3

Yeni düğüm (41, "Sena") zincirin başına eklenir: 41 → 21. buyukluk = 3.

0null141"Sena"21"Leyla"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(21, "Ali")kovaIndeksi = 21 % 10 = 1ilk.anahtar = 41 ≠ 21

h(21) = 1 → zincir taranır: ilk düğüm 41 ≠ 21 → ilk = ilk.sonraki.

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalaryerlestir(21, "Ali")ilk.anahtar = 21 = 21deger: "Leyla" → "Ali"buyukluk = 3

21 = 21 → anahtar zaten var: değer güncellenir ("Leyla" → "Ali"), return; buyukluk değişmez.

Ayrı Zincirleme · Java

Anahtar ile Değer Getirme

public String getir(Integer anahtar) {
  int kovaIndeksi = getKovaIndeksi(anahtar);
  HashDugumu ilk = kovalar[kovaIndeksi];
  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 · Java

Getirme: Adım Adım

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalarkovaSayisi = 10buyukluk = 3

Başlangıç: yerleştirmelerden sonraki tablo.

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalargetir(105)kovaIndeksi = 105 % 10 = 5ilk.anahtar = 105 ✓dönüş: "Murat"

h(105) = 5 → ilk düğüm 105 = 105 → "Murat" döner.

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalargetir(21)kovaIndeksi = 21 % 10 = 1ilk.anahtar = 41 ≠ 21

h(21) = 1 → ilk düğüm 41 ≠ 21 → ilk = ilk.sonraki.

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalargetir(21)ilk.anahtar = 21 ✓dönüş: "Ali"

İkinci düğüm 21 = 21 → "Ali" döner (2 karşılaştırma).

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalargetir(88)kovaIndeksi = 88 % 10 = 8ilk = nulldönüş: null

h(88) = 8 → kova 8 boş, döngüye hiç girilmez → null (anahtar yok).

Ayrı Zincirleme · Java

Hash Tablosundan Anahtar Silme

public String sil(Integer anahtar) {
  int kovaIndeksi = getKovaIndeksi(anahtar);
  HashDugumu ilk = kovalar[kovaIndeksi];
  HashDugumu onceki = null;
  while (ilk != null && !ilk.anahtar.equals(anahtar)) {
    onceki = ilk;
    ilk = ilk.sonraki;
  }
  if (ilk == null) return null;         // anahtar yok
  buyukluk--;
  if (onceki != null)
    onceki.sonraki = ilk.sonraki;       // ortadan / sondan
  else
    kovalar[kovaIndeksi] = ilk.sonraki; // baştan
  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 · Java

Silme: Adım Adım

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalarkovaSayisi = 10buyukluk = 3

Başlangıç: 3 çift. Sırayla sil(105), sil(88), sil(21), sil(41).

0null141"Sena"21"Ali"null2null3null4null5105"Murat"null6null7null8null9nullHashDugumu[] kovalarsil(105)kovaIndeksi = 105 % 10 = 5onceki = nullkovalar[5] = ilk.sonrakibuyukluk = 2 → "Murat"

h(105) = 5 → ilk düğüm eşleşti, onceki = null → kovalar[5] = null; "Murat" döner.

0null141"Sena"21"Ali"null2null3null4null5null6null7null8null9nullHashDugumu[] kovalarsil(88)kovaIndeksi = 88 % 10 = 8ilk = nulldönüş: nullbuyukluk = 2

h(88) = 8 → kova boş → null döner; buyukluk değişmez.

0null141"Sena"21"Ali"null2null3null4null5null6null7null8null9nullHashDugumu[] kovalarsil(21)kovaIndeksi = 21 % 10 = 141 ≠ 21 → onceki = 41

h(21) = 1 → 41 ≠ 21: onceki = 41, ilk = 21.

0null141"Sena"21"Ali"null2null3null4null5null6null7null8null9nullHashDugumu[] kovalarsil(21)21 = 21 ✓onceki.sonraki = ilk.sonrakibuyukluk = 1 → "Ali"

21 bulundu, onceki ≠ null → onceki.sonraki = null: 41'in bağı atlanır; "Ali" döner.

0null141"Sena"null2null3null4null5null6null7null8null9nullHashDugumu[] kovalarsil(41)kovaIndeksi = 41 % 10 = 1onceki = nullkovalar[1] = ilk.sonrakibuyukluk = 0 → "Sena"

h(41) = 1 → ilk düğüm eşleşti → kovalar[1] = null; "Sena" döner. Tablo boş.

Açık Adresleme

Açık Adresleme (Open Addressing)

  • Tüm çiftler dizinin kendisinde saklanır; her yuvada en fazla bir çift vardır (bu yüzden 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))İkinci fonksiyon hesabı; h₂(k) ≠ 0 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.

Açık Adresleme

Doğrusal Yoklama (Linear Probing)

h(k, i) = (k mod 13 + i) mod 13: dolu yuvaya gelen anahtar bir sonraki yuvaya (gerekirse başa sararak) bakar.

Eklenecek:18412244593231730124134518678922101112

h(18) = 18 mod 13 = 5, h(41) = 41 mod 13 = 2, h(22) = 22 mod 13 = 9 → üçü de ilk denemede boş yuvaya.

Eklenecek:1841224459323173012413451864478922101112✗i=0✓i=1

h(44) = 44 mod 13 = 5 → denenen: 5, 6 → 44 yuva 6'e (2 yoklama).

Eklenecek:184122445932317301241345186447598922101112✓i=0

h(59) = 59 mod 13 = 7 → yuva 7 boş → 59 yuva 7'e.

Eklenecek:18412244593231730124134518644759832922101112✗i=0✗i=1✓i=2

h(32) = 32 mod 13 = 6 → denenen: 6, 7, 8 → 32 yuva 8'e (3 yoklama).

Eklenecek:1841224459323173012413451864475983292210311112✗i=0✗i=1✗i=2✗i=3✗i=4✓i=5

h(31) = 31 mod 13 = 5 → denenen: 5, 6, 7, 8, 9, 10 → 31 yuva 10'e (6 yoklama).

Eklenecek:184122445932317301241345186447598329221031117312✗i=0✗i=1✗i=2✓i=3

h(73) = 73 mod 13 = 8 → denenen: 8, 9, 10, 11 → 73 yuva 11'e (4 yoklama). 5–11 arası tek büyük küme: birincil kümelenme.

Açık Adresleme

Karesel Yoklama (Quadratic Probing)

h(k, i) = (k mod 13 + i²) mod 13: sıçramalar 1, 4, 9, 16… büyür; aynı anahtarlar, farklı yerleşim.

Eklenecek:18412244593231730124134518678922101112

h(18) = 18 mod 13 = 5, h(41) = 41 mod 13 = 2, h(22) = 22 mod 13 = 9 → üçü de ilk denemede boş yuvaya.

Eklenecek:1841224459323173012413451864478922101112✗i=0✓i=1

h(44) = 44 mod 13 = 5 → denenen: 5, 6 → 44 yuva 6'e (2 yoklama).

Eklenecek:184122445932317301241345186447598922101112✓i=0

h(59) = 59 mod 13 = 7 → yuva 7 boş → 59 yuva 7'e.

Eklenecek:18412244593231730124134518644759892210321112✗i=0✗i=1✓i=2

h(32) = 32 mod 13 = 6 → denenen: 6, 7, 10 → 32 yuva 10'e (3 yoklama).

Eklenecek:1841224459323173013124134518644759892210321112✗i=0✗i=1✗i=2✓i=3

h(31) = 31 mod 13 = 5 → denenen: 5, 6, 9, 5+9=14 → 1 → 31 yuva 1'e (4 yoklama).

Eklenecek:184122445932317301312413451864475987392210321112✓i=0

h(73) = 73 mod 13 = 8 → yuva 8 boş → 73 yuva 8'e.

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

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 anahtar) {           // Goodrich: findSlot
  int h = Math.floorMod(anahtar, N), uygun = -1;
  for (int i = 0; i < N; i++) {
    int j = (h + i) % N;                       // doğrusal yoklama
    if (tablo[j] == null || tablo[j] == SILINDI) {
      if (uygun == -1) uygun = j;              // ilk boş / silindi yuva
      if (tablo[j] == null) break;             // boş yuva: anahtar kesin yok
    } else if (tablo[j].anahtar == anahtar)
      return j;                                // bulundu: indeks ≥ 0
  }
  return -(uygun + 1);                         // yok: eklenecek yer (negatif kodlu)
}
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

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.
  • Tablo yaklaşık %70–75 dolduğunda büyütülür.
λBaşarılıBaşarısız
0,501,52,5
0,752,58,5
0,905,550,5

Doğrusal yoklamada beklenen yoklama sayısı (Knuth):
½(1 + 1/(1−λ))  ve  ½(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): indeksler 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

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ı (Bölüm 10).

Performans ve Uygulamalar

Hash Tablolarının Kullanım Alanları

SözlükKelime → anlam; yazım denetiminde "bu kelime var mı?"
VeritabanıHash indeksleri 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.

Örnek Uygulama

Okul Numarası İçin Hash Fonksiyonu

SorunOkul numaralarını 1000 elemanlık 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 indeksine dönüştürmek.

Örnek Uygulama

Okul Numarası için Hash Fonksiyonu

hash(ab070c0de) = ((ab − 20) × 100 + (c − 6) × 500 + de) mod 1000
NumaraabcdeHesapİndeks
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ı indekse 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

public int calculateHash(String studentId) {             // "ab070c0de"
  int ab = Integer.parseInt(studentId.substring(0, 2));  // ab: kayıt yılı
  int c  = Integer.parseInt(studentId.substring(5, 6));  // c: bölüm
  int de = Integer.parseInt(studentId.substring(7, 9));  // de: kayıt sırası
  int yearFactor = (ab - 20) * 100;
  int departmentFactor = (c - 6) * 500;
  int hashValue = (yearFactor + departmentFactor + de) % 1000;
  return hashValue;
}
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.

Ö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.
KarmaşıklıkOrtalama O(1), en kötü O(n); yeniden boyutlandırma amortize O(1). Sıra bilgisi tutulmaz.

Ö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.
Önerilen deneme: aynı kovaya düşen anahtarlar ekleyin (N = 10 için 3, 13, 23…), zincirin uzamasını ve yük faktörü eşiği aşılınca tablonun büyümesini izleyin.

Ö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

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 9: Eşlemeler ve Hash Tabloları
Sonraki bölüm: Arama Ağaçları
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.

← → veya boşluk ile gezinin · F: tam ekran · Home/End: ilk/son slayt