Hash Tablosu

Anahtarları hash fonksiyonuyla kovalara dağıtıp ortalama O(1) erişim sağlayan veri yapısı.

Önce bunlara göz at

#Nasıl çalışır?

Hash tablosu (hash table), anahtarları bir hash fonksiyonuyla bir dizi indeksine dönüştürerek saklar. Bir anahtarı aramak için bütün veriyi gezmek gerekmez: hash hesaplanır ve doğrudan ilgili kovaya (bucket) gidilir.

Farklı anahtarlar aynı kovaya düşebilir; buna çakışma (collision) denir. Bu derste kullanılan ayrı zincirleme (separate chaining) yönteminde her kova, kendisine düşen anahtarların küçük bir listesini tutar.

İyi bir hash fonksiyonu anahtarları kovalara eşit dağıtır. Böylece zincirler kısa kalır ve ekleme, arama, silme ortalamada sabit sürede yapılır.

#Kendin dene

$ hash_table --interactive

Bir anahtar yaz ve ekle. Anahtar, hash fonksiyonuyla (anahtar mod 7) bir kovaya yerleşir; aynı kovaya düşenler zincir oluşturur.

Eleman: 5/14GeziliyorYeniÇıkarılıyorBulundu / okunan

#Temel işlemler

insert(key)
hash(key) ile kovayı bulur; anahtar orada yoksa zincirin sonuna ekler.
search(key)
Yalnızca hash(key) kovasındaki zinciri tarar.
remove(key)
Anahtarı kovasının zincirinde bulup çıkarır.
yük faktörü (load factor)
Eleman sayısı / kova sayısı. Belirli bir sınırı (ör. 0,75) geçince tablo büyütülür ve tüm anahtarlar yeniden hash'lenir.

#Örnek kod

Aynı algoritma 8 dilde. Her program derlenip çalıştırılarak test edildi ve hepsi aynı çıktıyı veriyor.

class HashSet:
    """Separate chaining: each bucket is a list of the keys that hash to it."""

    def __init__(self, bucket_count: int) -> None:
        self.buckets: list[list[int]] = [[] for _ in range(bucket_count)]

    def _hash(self, key: int) -> int:
        return key % len(self.buckets)

    def contains(self, key: int) -> bool:
        return key in self.buckets[self._hash(key)]

    def add(self, key: int) -> None:
        if not self.contains(key):
            self.buckets[self._hash(key)].append(key)

    def remove(self, key: int) -> None:
        chain = self.buckets[self._hash(key)]
        if key in chain:
            chain.remove(key)


s = HashSet(7)
for key in (15, 8, 22, 31):  # 15, 8 and 22 all hash to bucket 1
    s.add(key)

print(str(s.contains(22)).lower())
s.remove(8)
print(str(s.contains(8)).lower())
print(*s.buckets[1])

#Karmaşıklık

İşlemSüre
Ekleme (ortalama)O(1)
Arama (ortalama)O(1)
Silme (ortalama)O(1)
En kötü durum (hepsi aynı kovada)O(n)
Yeniden hash'lemeO(n), seyrek
BellekO(n)

Ortalama O(1), yük faktörü düşük tutulduğu ve hash fonksiyonu iyi dağıttığı sürece geçerlidir. Bütün anahtarlar aynı kovaya düşerse tablo bir bağlı listeye dönüşür ve işlemler O(n) olur. Büyütme işlemi tek seferde O(n) sürse de seyrek yapıldığı için eleman başına maliyeti sabittir (amortize O(1)).

#Ne zaman kullanılır?

Anahtarla hızlı erişim gereken her yerde: sözlükler ve kümeler (Python dict/set, Java HashMap, JavaScript Map/Set), önbellekler, sayaçlar, tekrar kontrolü. Elemanların sıralı gezilmesi ya da aralık sorguları gerekiyorsa dengeli ağaçlar daha uygundur.

#Alıştırma

Soruları cevaplamak ve ilerlemeni kaydetmek için giriş yap.

Kolay

1/3

Hash tablosunda arama ortalama hangi karmaşıklıktadır?

  • 1O(1)
  • 2O(log n)
  • 3O(n)
  • 4O(n log n)

Orta

1/1

7 kovalı bir tabloda hash(k) = k mod 7 ise 23 hangi kovaya düşer?

  • 12
  • 23
  • 31
  • 46

Zor

1/1

Hash tablosunda işlemler hangi durumda O(n)'e kadar yavaşlar?

  • 1Anahtarların çoğu aynı kovaya düştüğünde
  • 2Tablo boş olduğunda
  • 3Anahtarlar sayı olduğunda
  • 4Kova sayısı asal olduğunda

#Bundan sonra

Bu dersi bildiğini varsayan dersler: