Bölüm 12: Eşlemeler ve
Hash Tabloları
Giriş
Öğrenme Çıktıları
Anahtar-değer ilişkisini ve hash tablosunun temel işleyişini açıklamak.
Zincirleme ve açık adresleme yöntemlerini uygulamak.
Yük faktörü, yeniden boyutlandırma ve dağılım ilişkisini yorumlamak.
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.
Eşleme ve Hash Fikri
Eşleme ADT'sinin Temel İşlevleri
| İşlev | java.util.Map | Açı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ğer | size(), 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
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ındaki her k değeri A[k] hücresine yerleştirilir; kullanılmayan hücreler boş kalır.
ara(3): doğrudan A[3]'e bakılır → dolu → bulundu, O(1).
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ı
- 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.
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−1aralığında bir tam sayıya (indise) dönüştürür.
"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.
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
h(k) = k mod NN'yi asal seçmek, anahtarlardaki düzenli örüntülerin (10'un katları gibi) aynı kovalara yığılmasını önler.
h(k) = ((a·k + b) mod p) mod Np > N asal; a, b rastgele (a ≠ 0).
| k | k mod 10 | k mod 11 |
|---|---|---|
| 10 | 0 | 10 |
| 20 | 0 | 9 |
| 30 | 0 | 8 |
| 40 | 0 | 7 |
| 50 | 0 | 6 |
N = 10: hepsi aynı kovada. N = 11 (asal): hepsi farklı kovalarda.
Hash Fonksiyonları
equals ve hashCode Sözleşmesi
a.equals(b) doğruysa a.hashCode() == b.hashCode() olmak zorundadır; yoksa eşit anahtarlar farklı kovalara düşer.equals ile karşılaştırır.equals yeniden tanımlanıyorsa hashCode da aynı alanlara göre yeniden tanımlanmalıdır.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+)
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 (!)
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.
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!
Çö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ı
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.
long crc32Hash(String anahtar) { CRC32 crc = new CRC32(); // java.util.zip crc.update(anahtar.getBytes()); return crc.getValue(); }
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.
// Σ 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()
hash("Ali") çağrılır; her karakter Unicode koduyla (A = 65, l = 108, i = 105) işlenir.
h = 0 ile başlanır.
0 < 3 → döngü sürer; karakter s.charAt(0) = 'A' (65).
h = 31 · 0 + 65 = 65. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.
1 < 3 → döngü sürer; karakter s.charAt(1) = 'l' (108).
h = 31 · 65 + 108 = 2123. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.
2 < 3 → döngü sürer; karakter s.charAt(2) = 'i' (105).
h = 31 · 2123 + 105 = 65918. Önceki toplam 31 ile çarpılır: karakterin konumu da sonucu etkiler.
3 < 3 yanlış → döngü biter.
return 65918 = 65·31² + 108·31 + 105.
kod = 65918. Uzun dizelerde çarpım int sınırını aşar ve sonuç negatif olabilir.
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()
10 hücrelik boş tablo; her hücre null.
Döngü sıradaki anahtarı alır: k = 5.
modulusHash(5, 10): 5 mod 10 = 5 (bölümden kalan).
tablo[5] = 5.
Döngü sıradaki anahtarı alır: k = 1.
modulusHash(1, 10): 1 mod 10 = 1 (bölümden kalan).
tablo[1] = 1.
Döngü sıradaki anahtarı alır: k = 10.
modulusHash(10, 10): 10 mod 10 = 0 (bölümden kalan).
tablo[0] = 10.
Döngü sıradaki anahtarı alır: k = 26.
modulusHash(26, 10): 26 mod 10 = 6 (bölümden kalan).
tablo[6] = 26.
Döngü sıradaki anahtarı alır: k = 99.
modulusHash(99, 10): 99 mod 10 = 9 (bölümden kalan).
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:
Ayrı Zincirleme
Çakışma Çözümü: Ayrı Zincirleme
h(5) = 5 % 10 = 5 → kova 5 boş; (5, "Ali") düğümü kovaya bağlanır.
h(1) = 1 % 10 = 1 → kova 1 boş; (1, "Tuna") düğümü kovaya bağlanır.
h(10) = 10 % 10 = 0 → kova 0 boş; (10, "Leyla") düğümü kovaya bağlanır.
h(26) = 26 % 10 = 6 → kova 6 boş; (26, "Ceren") düğümü kovaya bağlanır.
h(99) = 99 % 10 = 9 → kova 9 boş; (99, "Sena") düğümü kovaya bağlanır.
h(105) = 105 % 10 = 5 → kova 5 dolu (5, "Ali"): çakışma. Yeni düğüm zincirin başına eklenir.
Ayrı Zincirleme
Hash Tablosu Terminolojisi
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; } } }
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); }
| anahtar | anahtar % 10 | Math.floorMod(anahtar, 10) | Sonuç |
|---|---|---|---|
| 105 | 5 | 5 | kova 5 |
| 21 | 1 | 1 | kova 1 |
| 41 | 1 | 1 | kova 1 (21 ile çakışır) |
| −7 | −7 | 3 | % ile kovalar[-7] → ArrayIndexOutOfBoundsException |
% 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)
getKovaIndisi(41) çağrılır; tabloda 10 kova var.
Integer.hashCode() sayının kendisidir: h = 41.
floorMod(41, 10) çağrılır.
Java'da 41 % 10 = 1.
r = 1 ≥ 0 → düzeltme gerekmez.
return 1.
Kova indisi 1. (21 de kova 1'e düşer: çakışma.)
getKovaIndisi(-7) çağrılır; tabloda 10 kova var.
Integer.hashCode() sayının kendisidir: h = -7.
floorMod(-7, 10) çağrılır.
Java'da -7 % 10 = -7: sonuç bölünenin işaretini alır.
r < 0 → r += 10 → r = 3. Artık 0 … 9 aralığında.
return 3.
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 }
h(anahtar) ile 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 }
yerlestir(21, "Leyla"): h(21) = 21 mod 10 = 1 → yalnızca kova 1'e bakılır.
Kova 1 boş: ilk = null.
ilk == null → döngü biter: 21 tabloda yok, yeni düğüm gerekir.
Yeni çift eklenecek: buyukluk = 2.
Yeni düğüm (21, "Leyla") oluşturulur; sonraki henüz null, tabloya bağlı değil.
dugum.sonraki = kovalar[1] = null (kova boştu).
kovalar[1] = dugum: yeni düğüm zincirin başında. Sona gitmeye gerek yok → O(1).
yerlestir(41, "Sena"): h(41) = 41 mod 10 = 1 → yalnızca kova 1'e bakılır.
ilk zincirin ilk düğümünü gösterir.
ilk != null → döngüye girilir, bu düğüm incelenecek.
21 ≠ 41 → eşleşme yok, zincirde ilerle.
ilk = ilk.sonraki → null (zincir bitti).
ilk == null → döngü biter: 41 tabloda yok, yeni düğüm gerekir.
Yeni çift eklenecek: buyukluk = 3.
Yeni düğüm (41, "Sena") oluşturulur; sonraki henüz null, tabloya bağlı değil.
dugum.sonraki eski baş düğümü (21) gösterir; mevcut zincir kaybolmaz.
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 }
yerlestir(21, "Ali"): h(21) = 21 mod 10 = 1 → yalnızca kova 1'e bakılır.
ilk zincirin ilk düğümünü gösterir.
ilk != null → döngüye girilir, bu düğüm incelenecek.
41 ≠ 21 → eşleşme yok, zincirde ilerle.
ilk = ilk.sonraki → düğüm 21.
ilk != null → döngüye girilir, bu düğüm incelenecek.
21.equals(21) → eşit: anahtar zaten tabloda.
Değer güncellenir: "Leyla" → "Ali". Yeni düğüm eklenmez.
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 }
| Çağrı | Kova | İncelenen düğümler | Dönüş |
|---|---|---|---|
tablo.getir(105) | 105 % 10 = 5 | 105 | "Murat" |
tablo.getir(21) | 21 % 10 = 1 | 41, 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 }
getir(105): h(105) = 105 mod 10 = 5. Diğer kovalara hiç bakılmaz.
ilk kovanın ilk düğümünü gösterir.
ilk != null → döngü gövdesine girilir.
105.equals(105) → eşit.
return "Murat" — 1 karşılaştırma yetti.
getir(21): h(21) = 21 mod 10 = 1. Diğer kovalara hiç bakılmaz.
ilk kovanın ilk düğümünü gösterir.
ilk != null → döngü gövdesine girilir.
41 ≠ 21 → aynı kovaya düşmüş başka bir anahtar (çakışma).
ilk = ilk.sonraki → düğüm 21.
ilk != null → döngü gövdesine girilir.
21.equals(21) → eşit.
return "Ali" — 2 karşılaştırma yetti.
getir(88): h(88) = 88 mod 10 = 8. Diğer kovalara hiç bakılmaz.
Kova 8 boş: ilk = null.
ilk == null → döngüye girilmez.
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; }
onceki.sonraki.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; }
sil(21): h(21) = 21 mod 10 = 1.
ilk baş düğümü gösterir.
onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.
41 ≠ 21 → koşul doğru, döngü gövdesine girilir.
onceki = ilk → düğüm 41.
ilk = ilk.sonraki → düğüm 21.
21.equals(21) → eşit: koşul yanlış, döngü biter. Silinecek düğüm bulundu.
ilk != null → anahtar var, silme yapılacak.
buyukluk = 2.
onceki != null → düğüm ortada/sonda: bağı onceki üzerinden atlanır.
onceki.sonraki = ilk.sonraki (null): 41 artık 21'i atlar; 21 zincirden koptu.
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; }
sil(105): h(105) = 105 mod 10 = 5.
ilk baş düğümü gösterir.
onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.
105.equals(105) → eşit: koşul yanlış, döngü biter. Silinecek düğüm bulundu.
ilk != null → anahtar var, silme yapılacak.
buyukluk = 1.
onceki == null → silinen düğüm zincirin başı: else dalı.
kovalar[5] = ilk.sonraki (null): kova artık 105'i göstermez.
return "Murat". Kopan düğümü çöp toplayıcı temizler.
sil(88): h(88) = 88 mod 10 = 8.
Kova 8 boş: ilk = null.
onceki = null: tek yönlü listede düğümü koparmak için bir önceki düğüm gerekir.
ilk == null → döngü hemen biter.
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öntem | f(i) | Yoklama dizisi (h(k) = 5, N = 13) | Zayıf yönü |
|---|---|---|---|
| Doğrusal (linear probing) | i | 5, 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ı |
Açık Adresleme · Kodu adım adım çalıştır
Doğrusal Yoklama (Linear Probing)
18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.
Sıradaki anahtar 44: h = 44 mod 13 = 5 (ev yuvası).
i = 0: j = (5 + 0) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1) mod 13 = 6 → yuva boş.
tablo[6] = 44, break (2 yoklama).
Sıradaki anahtar 59: h = 59 mod 13 = 7 (ev yuvası).
i = 0: j = (7 + 0) mod 13 = 7 → yuva boş.
tablo[7] = 59, break (1 yoklama).
Sıradaki anahtar 32: h = 32 mod 13 = 6 (ev yuvası).
i = 0: j = (6 + 0) mod 13 = 6 → yuva dolu (44), bir sonraki i.
i = 1: j = (6 + 1) mod 13 = 7 → yuva dolu (59), bir sonraki i.
i = 2: j = (6 + 2) mod 13 = 8 → yuva boş.
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ıradaki anahtar 31: h = 31 mod 13 = 5 (ev yuvası).
i = 0: j = (5 + 0) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1) mod 13 = 6 → yuva dolu (44), bir sonraki i.
i = 2: j = (5 + 2) mod 13 = 7 → yuva dolu (59), bir sonraki i.
i = 3: j = (5 + 3) mod 13 = 8 → yuva dolu (32), bir sonraki i.
i = 4: j = (5 + 4) mod 13 = 9 → yuva dolu (22), bir sonraki i.
i = 5: j = (5 + 5) mod 13 = 10 → yuva boş.
tablo[10] = 31, break (6 yoklama).
Sıradaki anahtar 73: h = 73 mod 13 = 8 (ev yuvası).
i = 0: j = (8 + 0) mod 13 = 8 → yuva dolu (32), bir sonraki i.
i = 1: j = (8 + 1) mod 13 = 9 → yuva dolu (22), bir sonraki i.
i = 2: j = (8 + 2) mod 13 = 10 → yuva dolu (31), bir sonraki i.
i = 3: j = (8 + 3) mod 13 = 11 → yuva boş.
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)
18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.
Sıradaki anahtar 44: h = 44 mod 13 = 5 (ev yuvası).
i = 0: j = (5 + 0²) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1²) mod 13 = 6 → yuva boş.
tablo[6] = 44, break (2 yoklama).
Sıradaki anahtar 59: h = 59 mod 13 = 7 (ev yuvası).
i = 0: j = (7 + 0²) mod 13 = 7 → yuva boş.
tablo[7] = 59, break (1 yoklama).
Sıradaki anahtar 32: h = 32 mod 13 = 6 (ev yuvası).
i = 0: j = (6 + 0²) mod 13 = 6 → yuva dolu (44), bir sonraki i.
i = 1: j = (6 + 1²) mod 13 = 7 → yuva dolu (59), bir sonraki i.
i = 2: j = (6 + 2²) mod 13 = 10 → yuva boş.
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ıradaki anahtar 31: h = 31 mod 13 = 5 (ev yuvası).
i = 0: j = (5 + 0²) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1²) mod 13 = 6 → yuva dolu (44), bir sonraki i.
i = 2: j = (5 + 2²) mod 13 = 9 → yuva dolu (22), bir sonraki i.
i = 3: j = (5 + 3²) mod 13 = 1 → yuva boş.
tablo[1] = 31, break (4 yoklama). Sıçramalar büyüdüğü için küme dışına çabuk çıkıldı.
Sıradaki anahtar 73: h = 73 mod 13 = 8 (ev yuvası).
i = 0: j = (8 + 0²) mod 13 = 8 → yuva boş.
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.
| k | 18 | 41 | 22 | 44 | 59 | 32 | 31 | 73 |
|---|---|---|---|---|---|---|---|---|
| h(k) = k mod 13 | 5 | 2 | 9 | 5 | 7 | 6 | 5 | 8 |
| h₂(k) (çakışmada) | — | — | — | 7−2 = 5 | — | — | 7−3 = 4 | — |
| Denenen yuvalar | 5 | 2 | 9 | 5, 10 | 7 | 6 | 5, 9, 0 | 8 |
Açık Adresleme · Kodu adım adım çalıştır
Kod İzleme: Çift Hash
18 → 5, 41 → 2, 22 → 9: üçü de ilk denemede boş yuvaya yerleşti. Sırada 44 var.
h = 44 mod 13 = 5 (ev yuvası), d = 7 − 2 = 5 (adım; asla 0 olmaz).
i = 0: j = (5 + 0·5) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1·5) mod 13 = 10 → yuva boş.
tablo[10] = 44, break (2 yoklama).
h = 59 mod 13 = 7 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).
i = 0: j = (7 + 0·4) mod 13 = 7 → yuva boş.
tablo[7] = 59, break (1 yoklama).
h = 32 mod 13 = 6 (ev yuvası), d = 7 − 4 = 3 (adım; asla 0 olmaz).
i = 0: j = (6 + 0·3) mod 13 = 6 → yuva boş.
tablo[6] = 32, break (1 yoklama).
h = 31 mod 13 = 5 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).
i = 0: j = (5 + 0·4) mod 13 = 5 → yuva dolu (18), bir sonraki i.
i = 1: j = (5 + 1·4) mod 13 = 9 → yuva dolu (22), bir sonraki i.
i = 2: j = (5 + 2·4) mod 13 = 0 → yuva boş.
tablo[0] = 31, break (3 yoklama).
h = 73 mod 13 = 8 (ev yuvası), d = 7 − 3 = 4 (adım; asla 0 olmaz).
i = 0: j = (8 + 0·4) mod 13 = 8 → yuva boş.
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
Doğrusal yoklama sonrası tablo. 44 silinmek isteniyor.
sil(44): h(44) = 5 → 18 ≠ 44 → yuva 6: 44 bulundu. Yuvayı null yapsak ne olur?
Yanlış yol: yuva 6 boşaltıldı. getir(32): h(32) = 6 → boş yuva → "yok" der. Oysa 32 yuva 8'de!
Doğru yol: yuvaya özel bir silindi işareti (DEFUNCT, tombstone ✗) konur.
getir(32): h = 6 → ✗ silindi, aramaya devam → 7: 59 ≠ 32 → 8: 32 bulundu.
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 }
j = yuvaBul(k); j ≥ 0 ise tablo[j].deger, değilse null.tablo[−(j+1)] yuvasına yeni çift.(h + i*i) % NÇift hash:
(h + i*h2) % NTablo 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ş (✗)
yuvaBul(32): h = 32 mod 13 = 6; henüz uygun yuva yok (−1).
i = 0: j = 6 → yuva SILINDI (✗).
İlk boş/silindi yuva: uygun = 6 (ekleme gerekirse buraya).
Silindi ≠ boş: aramaya devam (ötesinde 32 olabilir).
i = 1: j = 7 → dolu (59 ≠ 32), sonraki yuva.
i = 2: j = 8 → dolu, tablo[8].anahtar = 32 == 32.
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
yuvaBul(57): h = 57 mod 13 = 5; henüz uygun yuva yok (−1).
i = 0: j = 5 → dolu (18 ≠ 57), sonraki yuva.
i = 1: j = 6 → yuva SILINDI (✗).
İlk boş/silindi yuva: uygun = 6 (ekleme gerekirse buraya).
Silindi ≠ boş: aramaya devam (ötesinde 57 olabilir).
i = 2: j = 7 → dolu (59 ≠ 57), sonraki yuva.
i = 3: j = 8 → dolu (32 ≠ 57), sonraki yuva.
i = 4: j = 9 → dolu (22 ≠ 57), sonraki yuva.
i = 5: j = 10 → dolu (31 ≠ 57), sonraki yuva.
i = 6: j = 11 → dolu (73 ≠ 57), sonraki yuva.
i = 7: j = 12 → yuva boş.
uygun zaten 6: değişmez (ilk uygun yuva korunur).
Gerçekten boş yuva → 57 bundan ötede olamaz: break.
57 yok: return −(6 + 1) = -7 (negatif = "yok", içinde eklenecek yer).
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.
Doğrusal yoklamada beklenen yoklama sayısı (Knuth):
başarılı ½(1 + 1/(1−λ)) · başarısız ½(1 + 1/(1−λ)²)
Performans ve Uygulamalar
Yeniden Boyutlandırma (Rehashing)
N = 5 kovalı tabloya 12, 7, 20, 33 eklendi. λ = 0,80 eşiği aştı → yeniden boyutlandırma (resizing).
Yeni dizi ayrılır ve her anahtar yeniden hash'lenir (rehashing): indisler değişir, eski konumlar geçersizdir.
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; } } }
λ = 4/5 = 0,8 > 0,75 olduğu için çağrıldı. eski eski diziyi tutar.
yeniN = 2·5 + 1 = 11: yaklaşık iki kat ve tek sayı (burada asal).
kovalar artık 11 boş kovalı yeni diziyi gösterir.
buyukluk = 0: yerlestir her çifti yeniden sayacak.
Eski kova 0: ilk = düğüm 20.
yerlestir(20): eskiden 20 mod 5 = 0, şimdi 20 mod 11 = 9 → yeni kova 9.
ilk = null: bu zincir bitti.
Eski kova 1 boş: ilk = null, while'a girilmez.
Eski kova 2: ilk = düğüm 7.
yerlestir(7): eskiden 7 mod 5 = 2, şimdi 7 mod 11 = 7 → yeni kova 7.
ilk = ilk.sonraki → düğüm 12 (aynı eski zincir).
yerlestir(12): eskiden 12 mod 5 = 2, şimdi 12 mod 11 = 1 → yeni kova 1.
ilk = null: bu zincir bitti.
Eski kova 3: ilk = düğüm 33.
yerlestir(33): eskiden 33 mod 5 = 3, şimdi 33 mod 11 = 0 → yeni kova 0.
ilk = null: bu zincir bitti.
Eski kova 4 boş: ilk = null, while'a girilmez.
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ı | getir | yerlestir | sil |
|---|---|---|---|
| Sırasız dizi / liste | O(n) | O(n)* | O(n) |
| Sıralı dizi (ikili arama) | O(log n) | O(n) | O(n) |
| Hash tablosu: ortalama | O(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.
Performans ve Uygulamalar
Java HashMap İçeride Nasıl Çalışır?
& (n − 1) maskesi.h ^ (h >>> 16) üst bitleri de indise katar.Performans ve Uygulamalar · Kodu adım adım çalıştır
Kod İzleme: indis(k, 16) — karıştır, maskele
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.
Integer.hashCode() değerin kendisidir: h = 1 = 0x00000001. Maskesiz kova h & 15 = 1 olurdu (kesikli).
h >>> 16 = 0: üst yarı alta kayar. XOR ile m = 1; artık alt 4 bit = 0001.
m & 15 = 1 → anahtar 1 kova 1'e gider ve yazdırılır.
Integer.hashCode() değerin kendisidir: h = 65537 = 0x00010001. Maskesiz kova h & 15 = 1 olurdu (kesikli).
h >>> 16 = 1: üst yarı alta kayar. XOR ile m = 65536; artık alt 4 bit = 0000.
m & 15 = 0 → anahtar 65537 kova 0'a gider ve yazdırılır.
Integer.hashCode() değerin kendisidir: h = 131073 = 0x00020001. Maskesiz kova h & 15 = 1 olurdu (kesikli).
h >>> 16 = 2: üst yarı alta kayar. XOR ile m = 131075; artık alt 4 bit = 0011.
m & 15 = 3 → anahtar 131073 kova 3'e gider ve yazdırılır.
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ı
HashMap, HashSet.Performans ve Uygulamalar · Kodu adım adım çalıştır
Uygulama: ikiToplam([8, 3, 11, 5, 7], 12)
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.
i = 0: a[0] = 8, gereken eş ek = 4. containsKey(4) → false: 4 daha önce görülmedi.
gor.put(8, 0): sonraki öğeler 8'i eş olarak arayabilsin.
i = 1: a[1] = 3, gereken eş ek = 9. containsKey(9) → false: 9 daha önce görülmedi.
gor.put(3, 1): sonraki öğeler 3'i eş olarak arayabilsin.
i = 2: a[2] = 11, gereken eş ek = 1. containsKey(1) → false: 1 daha önce görülmedi.
gor.put(11, 2): sonraki öğeler 11'i eş olarak arayabilsin.
i = 3: a[3] = 5, gereken eş ek = 7. containsKey(7) → false: 7 daha önce görülmedi.
gor.put(5, 3): sonraki öğeler 5'i eş olarak arayabilsin.
i = 4: ek = 5, containsKey(5) true (O(1) ortalama) → a[3] + a[4] = 5 + 7 = 12. return [3, 4].
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
Numara biçimi: ab070c0de · örnek: 210707003
- 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
| Numara | ab | c | de | Hesap | İndis |
|---|---|---|---|---|---|
210706003 | 21 | 6 | 03 | (1×100 + 0×500 + 3) mod 1000 | 103 |
240707050 | 24 | 7 | 50 | (4×100 + 1×500 + 50) mod 1000 | 950 |
230707042 | 23 | 7 | 42 | (3×100 + 1×500 + 42) mod 1000 | 842 |
Ö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(5, 6) yalnızca 5. karakteri (c) verir; substring(7, 9) 7. ve 8. karakterleri (de).calculateHash("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()
calculateHash("240707050"): indisler 0 … 8.
substring(0, 2) = "24" (bitiş hariç) → ab = 24 (kayıt yılı).
substring(5, 6) = "7" → c = 7 (bölüm).
substring(7, 9) = "50" → de = 50 (kayıt sırası).
yil = (24 − 20) · 100 = 400: her yıl 100'lük bir blok.
bolum = (7 − 6) · 500 = 500: bölüm 7 ise 500 kaydırılır.
return (400 + 500 + 50) % 1000 = 950.
calculateHash("210706003"): indisler 0 … 8.
substring(0, 2) = "21" (bitiş hariç) → ab = 21 (kayıt yılı).
substring(5, 6) = "6" → c = 6 (bölüm).
substring(7, 9) = "03" → de = 3 (kayıt sırası).
yil = (21 − 20) · 100 = 100: her yıl 100'lük bir blok.
bolum = (6 − 6) · 500 = 0: bölüm 6 ise kaydırma yok.
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
put, get, remove; aynı anahtarla put = güncelleme.mod N, N asal). Belirleyici, hızlı, düzgün dağılımlı.Özet
Etkileşimli Simülatörler
Ö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.equals ezmekEşit anahtarlar farklı kovaya düşer, get null döner. İkisi birlikte ezilir.Integer ile ==−128…127 dışındaki kutulu sayılar ayrı nesnelerdir; == false verir, equals kullanın.null ile silmekYoklama zinciri kopar; "silindi" (DEFUNCT) işareti gerekir.HashMap gezinme sırası ekleme sırası değildir: LinkedHashMap ya da TreeMap.Özet
Örnek Problemler
- N = 7, h(k) = k mod 7, doğrusal yoklama: 10, 17, 24, 3, 5 sırayla eklenirse son tablo nedir?
- Aynı anahtarları karesel yoklama ile ekleyin.
- Zincirlemeli, N = 10 kovalı bir tabloda 25 çift var. λ nedir? Başarısız bir arama ortalama kaç düğüm inceler?
- Açık adreslemede silinen yuvayı neden doğrudan
nullyapamayız? - 230706015 numarası için hash(ab070c0de) değerini hesaplayın.
- 0, 12, 24, 36 anahtarları N = 12 ve N = 11 ile
k mod Nkullanıldığında hangi kovalara düşer?
Cevapları göster
- 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.
- 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.
- λ = 25/10 = 2,5; başarısız arama ortalama λ = 2,5 düğüm inceler (tüm zincir taranır).
- Yoklama zinciri kopar: o yuvanın ötesine yerleşmiş anahtarlar "yok" sanılır. Çözüm: "silindi" (DEFUNCT) işareti.
- ab = 23, c = 6, de = 15 → (300 + 0 + 15) mod 1000 = 315.
- N = 12: hepsi 0 (çakışma). N = 11: 0, 1, 2, 3.
Özet
Alıştırmalar
- N = 11, h(k) = k mod 11, h₂(k) = 7 − (k mod 7) ile çift hash: 14, 25, 36, 3 hangi yuvalara yerleşir?
- N = 5 zincirli tablodaki 12, 7, 20, 33 anahtarları N = 11'e büyütülünce hangi kovalara gider?
- Doğrusal yoklamada λ = 0,8 iken başarısız bir arama ortalama kaç yoklama yapar?
- Bir kelime dizisindeki her kelimenin kaç kez geçtiğini
HashMapile sayan döngüyü yazın. hashCode()her nesne için 42 döndürürse tablo nasıl davranır?indis(32, 16)veindis(65568, 16)nedir? Karıştırma olmasa?
Cevap ve ipuçları
- Denenen yuvalar: 14→3; 25→3, 6; 36→3, 9; 3→3, 7.
- k mod 11: 12→1, 7→7, 20→9, 33→0 (hepsi yeniden hash'lenir).
- ½(1 + 1/(1 − 0,8)²) = ½(1 + 25) = 13.
for (String w : k) f.put(w, f.getOrDefault(w, 0) + 1);→ O(n) ortalama.- Hepsi tek kovaya düşer: işlemler O(n). Java 8+ o kovayı ağaca çevirir (anahtarlar Comparable ise O(log n)).
- 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,HashMapveHashSetAPI belgeleri.