O(1) Arama, Ekleme ve Silme - Detaylı Anlatım
Hash Table (diğer adlarıyla Hash Map, Dictionary, Associative Array), anahtar-değer (key-value) çiftlerini depolayan ve ortalama O(1) yani sabit zamanda arama, ekleme, silme yapabilen güçlü bir veri yapısıdır.
Normal bir dizide "Ali" isimli kişiyi bulmak için tüm elemanları tek tek kontrol etmeniz gerekir → O(n)
Hash Table ise anahtarı matematiksel bir formülle doğrudan bellek adresine çevirir → O(1)
| Yöntem | İşlem | Karmaşıklık |
|---|---|---|
| Liste ile Arama | Baştan sona tüm elemanları kontrol et | O(n) - 1 milyon kayıt = 1 milyon kontrol |
| Hash Table | hash("Ali") → direkt indekse git | O(1) - 1 milyon kayıt = 1 hesaplama! |
Hash Fonksiyonu, herhangi bir veriyi (string, sayı, obje) sabit boyutlu bir sayıya dönüştüren matematiksel fonksiyondur.
Formül: indeks = hash(anahtar) % tablo_boyutu
% (modülo) operatörü, bölümden kalanı verir. Bu sayede her zaman 0 ile (tablo_boyutu-1) arası bir indeks elde ederiz.
# Python'un yerleşik hash() fonksiyonu
# Her obje için benzersiz bir tamsayı üretir
# String'lerin hash değerleri
print("String hash değerleri:")
print(f"hash('Ali') = {hash('Ali')}")
print(f"hash('Ayşe') = {hash('Ayşe')}")
print(f"hash('ali') = {hash('ali')}") # Büyük/küçük harf fark eder!
# Sayıların hash değerleri (kendileri)
print(f"\nhash(42) = {hash(42)}")
print(f"hash(3.14) = {hash(3.14)}")
# Tuple hash'lenebilir, list hash'lenemez!
print(f"\nhash((1, 2, 3)) = {hash((1, 2, 3))}")
# hash([1, 2, 3]) # TypeError: unhashable type: 'list'
# Modülo ile indeks hesaplama
tablo_boyutu = 11
anahtar = "Ali"
indeks = hash(anahtar) % tablo_boyutu
print(f"\nTablo boyutu: {tablo_boyutu}")
print(f"'{anahtar}' için indeks: {indeks}")
hash() değerleri program her çalıştığında değişebilir (güvenlik nedeniyle). Bu yüzden hash değerlerini dosyaya kaydetmeyin!
11 hücreli bir hash table ile deney yapın. Anahtar-değer çiftleri ekleyin ve çarpışmaları gözlemleyin!
Farklı anahtarlar aynı indekse hash'lendiğinde çarpışma (collision) oluşur. Bu kaçınılmazdır çünkü sonsuz sayıda anahtar, sınırlı sayıda hücreye eşlenir.
hash("Ali") % 11 = 3
hash("Veli") % 11 = 3 ← Aynı indeks! ÇARPIŞMA!
Her hücrede bir linked list tutulur. Çarpışan elemanlar listeye eklenir.
✅ Avantaj: Silme kolay, tablo hiç dolmaz
❌ Dezavantaj: Ek bellek (pointer'lar)
Çarpışmada başka boş hücre aranır. Tüm veriler tablonun içinde kalır.
✅ Avantaj: Cache-friendly (veriler bitişik)
❌ Dezavantaj: Clustering problemi
* Ortalama durum, iyi bir hash fonksiyonu ve düşük load factor ile
Kötü bir hash fonksiyonu tüm anahtarları aynı indekse atarsa, hash table linked list'e dönüşür → O(n). Bu yüzden iyi hash fonksiyonu kritik!
Python'un dict (dictionary) yapısı, C dilinde yazılmış yüksek performanslı bir hash table implementasyonudur.
# ===== PYTHON DICT TEMELLERİ =====
# 1. Oluşturma - Süslü parantez {} ile
ogrenciler = {
"Ali": 85, # anahtar: değer
"Ayşe": 92,
"Mehmet": 78
}
print(f"Öğrenciler: {ogrenciler}")
# 2. Ekleme - O(1) ortalama
# Yeni bir anahtar ile değer atama
ogrenciler["Fatma"] = 88
ogrenciler["Zeynep"] = 95
print(f"\nYeni eklemeler: {ogrenciler}")
# 3. Arama/Erişim - O(1) ortalama
# Köşeli parantez ile değere erişim
ali_notu = ogrenciler["Ali"]
print(f"\nAli'nin notu: {ali_notu}")
# 4. Güncelleme - O(1) ortalama
# Var olan anahtara yeni değer atama
ogrenciler["Ali"] = 90 # 85'ten 90'a güncellendi
print(f"Ali'nin yeni notu: {ogrenciler['Ali']}")
# 5. Silme - O(1) ortalama
# del anahtar kelimesi ile
del ogrenciler["Mehmet"]
print(f"\nMehmet silindi: {ogrenciler}")
# 6. Anahtar Kontrolü - O(1) ortalama
# 'in' operatörü ile
print(f"\n'Ayşe' var mı? {'Ayşe' in ogrenciler}") # True
print(f"'Zehra' var mı? {'Zehra' in ogrenciler}") # False
# 7. Güvenli Erişim - get() metodu
# Anahtar yoksa hata vermez, varsayılan değer döner
zehra_notu = ogrenciler.get("Zehra", "Kayıt yok")
print(f"Zehra'nın notu: {zehra_notu}")
# 8. Tüm Anahtarlar ve Değerler
print(f"\nTüm anahtarlar: {list(ogrenciler.keys())}")
print(f"Tüm değerler: {list(ogrenciler.values())}")
print(f"Tüm çiftler: {list(ogrenciler.items())}")
# ===== DİCT COMPREHENSION =====
# Liste comprehension'ın dict versiyonu
# Sözdizimi: {anahtar: değer for eleman in iterable}
# 1-10 arası sayıların kareleri
kareler = {x: x**2 for x in range(1, 11)}
print(f"Kareler: {kareler}")
# ===== SETDEFAULT =====
# Anahtar yoksa ekle, varsa mevcut değeri döndür
sayac = {}
kelimeler = ["elma", "armut", "elma", "kiraz", "elma"]
for kelime in kelimeler:
# Eğer kelime yoksa 0 ile başlat, sonra 1 ekle
sayac[kelime] = sayac.get(kelime, 0) + 1
print(f"\nKelime sayıları: {sayac}")
# ===== DEFAULTDICT =====
# collections modülünden - varsayılan değer fabrikası
from collections import defaultdict
# int() varsayılan olarak 0 döner
sayac2 = defaultdict(int)
for kelime in kelimeler:
sayac2[kelime] += 1 # Olmayan anahtar otomatik 0 olur
print(f"defaultdict ile: {dict(sayac2)}")
# list() varsayılan olarak [] döner
gruplar = defaultdict(list)
ogrenciler = [("A", "Ali"), ("B", "Ayşe"), ("A", "Ahmet"), ("B", "Berk")]
for sinif, isim in ogrenciler:
gruplar[sinif].append(isim)
print(f"\nSınıf grupları: {dict(gruplar)}")
# ===== COUNTER =====
# Sayma işlemi için özel dict
from collections import Counter
metin = "merhaba dünya merhaba python"
kelime_sayisi = Counter(metin.split())
print(f"\nCounter ile: {kelime_sayisi}")
print(f"En sık 2 kelime: {kelime_sayisi.most_common(2)}")
{x: x**2 for x in range(5)} → Liste comprehension gibi ama dict üretir. x: x**2 anahtar-değer çiftini tanımlar.
d.get("yok", 0) → KeyError vermez, 0 döner.
int → 0, list → [], str → ""
most_common(n) en sık n elemanı döner.
class HashTable:
"""
Chaining yöntemi ile Hash Table implementasyonu.
Her bucket bir liste (linked list yerine Python list).
"""
def __init__(self, size=11):
"""
Hash table oluştur.
Args:
size: Bucket sayısı (asal sayı tercih edilir)
"""
self.size = size
# Her bucket için boş liste oluştur
self.buckets = [[] for _ in range(size)]
self.count = 0 # Eleman sayısı
def _hash(self, key):
"""Anahtarı indekse çevir."""
return hash(key) % self.size
def put(self, key, value):
"""Anahtar-değer çifti ekle veya güncelle."""
index = self._hash(key)
bucket = self.buckets[index]
# Anahtar zaten var mı kontrol et
for i, (k, v) in enumerate(bucket):
if k == key:
bucket[i] = (key, value) # Güncelle
return
# Yeni eleman ekle
bucket.append((key, value))
self.count += 1
def get(self, key):
"""Anahtarın değerini döndür."""
index = self._hash(key)
bucket = self.buckets[index]
for k, v in bucket:
if k == key:
return v
raise KeyError(f"Anahtar bulunamadı: {key}")
def remove(self, key):
"""Anahtarı sil."""
index = self._hash(key)
bucket = self.buckets[index]
for i, (k, v) in enumerate(bucket):
if k == key:
del bucket[i]
self.count -= 1
return v
raise KeyError(f"Anahtar bulunamadı: {key}")
def __contains__(self, key):
"""'in' operatörü için."""
try:
self.get(key)
return True
except KeyError:
return False
def __str__(self):
"""Görselleştirme."""
result = []
for i, bucket in enumerate(self.buckets):
if bucket:
items = ", ".join(f"{k}:{v}" for k, v in bucket)
result.append(f"[{i}] → {items}")
else:
result.append(f"[{i}] → boş")
return "\n".join(result)
# Test
ht = HashTable(7)
# Eklemeler
for isim, yas in [("Ali", 25), ("Ayşe", 22), ("Mehmet", 30),
("Fatma", 28), ("Zeynep", 24), ("Ahmet", 27)]:
ht.put(isim, yas)
print(f"Eklendi: {isim} → {yas}")
print(f"\n=== Hash Table Durumu ===")
print(ht)
print(f"\nToplam eleman: {ht.count}")
# Arama
print(f"\nAli'nin yaşı: {ht.get('Ali')}")
print(f"'Zeynep' var mı? {'Zeynep' in ht}")
def two_sum(nums, target):
"""
Dizide toplamı target olan iki sayının indekslerini bul.
Brute Force: O(n²) - Her çift için kontrol
Hash Table: O(n) - Tek geçiş!
Mantık: Her sayı için "target - sayı" değerini hash'te ara.
"""
# Gördüğümüz sayıları ve indekslerini sakla
seen = {} # {değer: indeks}
for i, num in enumerate(nums):
# enumerate(liste): (indeks, değer) çiftleri döner
complement = target - num # Aranan tamamlayıcı
if complement in seen:
# Bulundu! Tamamlayıcının indeksi ve şimdiki indeks
return [seen[complement], i]
# Bu sayıyı kaydet
seen[num] = i
return [] # Bulunamadı
# Test
nums = [2, 7, 11, 15]
target = 9
print(f"Dizi: {nums}")
print(f"Hedef: {target}")
print(f"Sonuç: {two_sum(nums, target)}") # [0, 1] çünkü 2 + 7 = 9
nums2 = [3, 2, 4]
target2 = 6
print(f"\nDizi: {nums2}")
print(f"Hedef: {target2}")
print(f"Sonuç: {two_sum(nums2, target2)}") # [1, 2] çünkü 2 + 4 = 6
def first_repeating(s):
"""
String'deki ilk tekrar eden karakteri bul.
Hash Set kullanarak O(n) çözüm.
Set, sadece anahtarları tutan bir hash table'dır.
"""
seen = set() # Gördüğümüz karakterler
for char in s:
if char in seen: # O(1) kontrol
return char
seen.add(char) # O(1) ekleme
return None # Tekrar yok
# Test
test_strings = ["abcabc", "abcdef", "aabbcc", "python"]
for s in test_strings:
result = first_repeating(s)
if result:
print(f"'{s}' → İlk tekrar: '{result}'")
else:
print(f"'{s}' → Tekrar yok")
Python'un dict implementasyonu çok optimize edilmiştir. Python 3.7+ ile dict'ler ekleme sırasını korur.
Veritabanları kayıtları hızlı bulmak için hash indeksleri kullanır. SELECT * WHERE id = 12345 → O(1)
Şifreler düz metin değil, hash'lenmiş hali saklanır. bcrypt, sha256 gibi güvenli hash fonksiyonları kullanılır.
Tarayıcınız domain → IP eşlemelerini hash table'da cache'ler. google.com → 142.250.185.78
Redis, Memcached gibi sistemler tamamen hash table tabanlıdır. Web sitelerinde sık erişilen veriler cache'lenir.
Python'da her şey dict: global/local değişkenler, class attribute'ları, module namespace'leri...
Symbol table'lar hash table'dır. Değişken isimlerini hızlıca bulup türünü, adresini getirir.
| İşlem | Array | Linked List | BST (Dengeli) | Hash Table |
|---|---|---|---|---|
| Arama (key) | O(n) | O(n) | O(log n) | O(1)* |
| Ekleme | O(n) | O(1)** | O(log n) | O(1)* |
| Silme | O(n) | O(n) | O(log n) | O(1)* |
| Sıralı Gezinme | ✅ O(n) | ✅ O(n) | ✅ O(n) | ❌ Sıra yok |
| Min/Max Bulma | O(n) | O(n) | O(log n) | O(n) |
* Ortalama durum, ** Başa ekleme