🏔️ 9.1: Heap (Yığın) Veri Yapısı

Priority Queue'nun Temel Yapısı - Detaylı Anlatım

📌 Heap Nedir?

Diyelim ki bir oyun yapıyorsunuz ve sürekli olarak "en güçlü düşman hangisi?" sorusunu cevaplamak zorundasınız. Ya da bir hastane simülasyonunda "en acil hasta kim?" sorusu. Her seferinde tüm listeyi taramak O(n) zaman alır - büyük veri setlerinde bu çok yavaş!

Heap tam da bu sorunu çözer: En büyük veya en küçük elemana anında (O(1)) erişim sağlar, üstelik ekleme ve silme işlemleri de sadece O(log n) sürer!

Teknik olarak Heap, tam ikili ağaç (complete binary tree) şeklinde düzenlenen ve heap özelliğini koruyan özel bir veri yapısıdır.

📚 Ön Bilgi: Heap'i anlamak için ağaç kavramlarını bilmeniz gerekir. Ağaç Temelleri sayfasında derinlik, yükseklik, complete binary tree gibi kavramları öğrenebilirsiniz.

📚 Temel Terimler - Bunları Anlamadan Devam Etmeyin!

Tam İkili Ağaç (Complete Binary Tree): Normal bir ikili ağaçtan farklı olarak, tüm seviyeler (son hariç) tamamen doludur ve son seviye de soldan sağa doldurulur. Boşluk olamaz! Bu özel yapı sayesinde ağacı dizi ile temsil edebiliyoruz - pointer gerekmez.
Heap Özelliği: Her parent (ebeveyn) düğüm, çocuklarından büyük (max heap) veya küçük (min heap) olmalı. Bu kural kök'ten yapraklara kadar her düğümde geçerli olmalı.
Priority Queue: Öncelik kuyruğu. Her elemana öncelik atanır, en yüksek öncelikli eleman ilk çıkar. Heap ile O(log n) ekleme/çıkarma.

🔺 Max Heap (Azami Yığın)

Her düğüm ≥ çocukları

Kök = En Büyük

parent(i) ≥ child(i)

🔻 Min Heap (Asgari Yığın)

Her düğüm ≤ çocukları

Kök = En Küçük

parent(i) ≤ child(i)

🌳 Heap Görselleştirmesi

Max Heap Örneği (15 Elemanlı)

90 [0] 85 [1] 70 [2] 40 [3] 80 [4] 30 [5] 25 [6] 10 [7] 35 [8] 75 [9] 50 [10] 20 [11] 15 [12] 5 [13] 22 [14]

📦 Dizi Temsili

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
90
85
70
40
80
30
25
10
35
75
50
20
15
5
22

Heap tam ikili ağaç olduğu için, dizi ile verimli temsil edilir. Pointer gerekmez!

🔢 İndeks Formülleri

Heap'i dizi olarak tutarken, parent-child ilişkileri matematiksel formüllerle bulunur:

i indeksindeki düğüm için (0-tabanlı / 0-indexed):

  • 👆 Parent indeksi (Parent index): (i - 1) // 2
  • 👈 Sol çocuk indeksi (Left child index): 2 * i + 1
  • 👉 Sağ çocuk indeksi (Right child index): 2 * i + 2
İndeks Hesaplama Örnekleri
# Heap indeks formülleri

def parent(i):
    """i'nin parent indeksini döndür"""
    return (i - 1) // 2

def left_child(i):
    """i'nin sol çocuk indeksini döndür"""
    return 2 * i + 1

def right_child(i):
    """i'nin sağ çocuk indeksini döndür"""
    return 2 * i + 2

# Heap dizisi
heap = [90, 85, 70, 40, 80, 30, 25, 10, 35, 75, 50, 20, 15, 5, 22]
print(f"Heap: {heap}")
print(f"Boyut: {len(heap)} eleman\n")

# Örnek hesaplamalar
for i in [0, 1, 4, 7]:
    print(f"=== İndeks {i} (değer: {heap[i]}) ===")

    p = parent(i)
    l = left_child(i)
    r = right_child(i)

    if i > 0:
        print(f"  Parent: indeks {p} → değer {heap[p]}")
    else:
        print(f"  Parent: Kök düğüm, parent yok")

    if l < len(heap):
        print(f"  Sol çocuk: indeks {l} → değer {heap[l]}")
    else:
        print(f"  Sol çocuk: yok (yaprak düğüm)")

    if r < len(heap):
        print(f"  Sağ çocuk: indeks {r} → değer {heap[r]}")
    else:
        print(f"  Sağ çocuk: yok")
    print()
Çıktı bekleniyor...

🎮 İnteraktif Min Heap Simülasyonu

📦 Dizi Temsili

⚙️ Temel İşlemler

1. Insert / Ekleme - Heapify Up (Yukarı Yüzdürme)

  1. Sona ekle: Yeni elemanı dizinin sonuna ekle
  2. Heapify Up (Yukarı yüzdür): Parent (ebeveyn) ile karşılaştır
    • Min Heap: Parent > yeni eleman ise takas yap
    • Max Heap: Parent < yeni eleman ise takas yap
  3. Tekrarla: Köke (root) ulaşana veya heap özelliği sağlanana kadar yukarı çık

Karmaşıklık (Complexity): O(log n) - En fazla ağaç yüksekliği kadar takas

2. Extract Min/Max (Kökü Çıkarma) - Heapify Down (Aşağı Batırma)

  1. Kökü al: En üstteki elemanı kaydet (min veya max)
  2. Son elemanı köke taşı: Dizinin son elemanını köke (root) koy
  3. Heapify Down (Aşağı batır): Çocuklarla (children) karşılaştır
    • Min Heap: Küçük çocukla takas yap (eğer parent > çocuk)
    • Max Heap: Büyük çocukla takas yap (eğer parent < çocuk)
  4. Tekrarla: Yaprak düğüme (leaf node) ulaşana veya heap özelliği sağlanana kadar aşağı in

Karmaşıklık (Complexity): O(log n) - En fazla ağaç yüksekliği kadar takas

📊 Zaman Karmaşıklığı

İşlem Zaman Açıklama
Find Min/Max (Peek / Gözetleme) O(1) Kök her zaman min/max, direkt oku
Insert / Push (Ekleme) O(log n) Sona ekle + heapify up (yukarı yüzdür)
Extract Min/Max / Pop (Çıkarma) O(log n) Kökü çıkar + heapify down (aşağı batır)
Build Heap / Heapify (Heap Oluşturma) O(n) Aşağıdan yukarıya heapify (O(n log n) değil!)
Heap Sort (Heap Sıralaması) O(n log n) n kez extract işlemi
Arbitrary Search (Rastgele Arama) O(n) Rastgele eleman arama - heap bunun için değil!
💡 Neden O(log n)?

Tam ikili ağacın yüksekliği log₂(n)'dir. Heapify işlemleri en fazla yükseklik kadar adım atar.

🐍 Python heapq Modülü

⚠️ Önemli: Python'un heapq modülü sadece Min Heap destekler!

heapq Temel Kullanım
import heapq

# ===== MIN HEAP TEMELLERİ =====
print("=== MIN HEAP İŞLEMLERİ ===\n")

# 1. Boş heap oluştur (sadece boş liste!)
heap = []

# 2. Insert / Ekleme - heappush(heap, item)
# Her ekleme sonrası heap özelliği korunur - O(log n)
print("İşlem 1: Insert (Ekleme)")
for val in [35, 10, 25, 5, 15, 30, 20]:
    heapq.heappush(heap, val)
    print(f"  {val} eklendi → heap: {heap}")

# 3. Peek / Find Min - En küçüğe bakma (silmeden)
# Sadece heap[0] okumak - O(1)
print(f"\nİşlem 2: Peek / Find Min (Gözetleme)")
print(f"  En küçük eleman: {heap[0]} ← Sadece bak, silme!")
print(f"  Heap değişmedi: {heap}")

# 4. Extract Min - En küçüğü çıkarma
# heappop(heap) - O(log n)
print(f"\nİşlem 3: Extract Min (Minimum Çıkarma)")
min_val = heapq.heappop(heap)
print(f"  Çıkarılan: {min_val}")
print(f"  Yeni min: {heap[0]}, Heap: {heap}")

# 5. Tüm elemanları sırayla çıkar
print("\nTüm elemanları çıkarıyoruz (sıralı çıkacak!):")
result = [min_val]  # İlk çıkanı da ekle
while heap:
    result.append(heapq.heappop(heap))

print(f"Çıkış sırası (küçükten büyüğe): {result}")
Çıktı bekleniyor...

🔄 Listeden Heap Oluşturma

heapify ve Diğer Fonksiyonlar
import heapq

# ===== HEAPIFY - Listeyi Heap'e Çevir =====
print("=== HEAPIFY ===")

# Normal liste
data = [35, 10, 25, 5, 15, 30, 20, 8, 12]
print(f"Orijinal liste: {data}")

# heapify() - Listeyi IN-PLACE heap'e çevirir
# Karmaşıklık: O(n) - tek tek eklemekten daha hızlı!
heapq.heapify(data)
print(f"Heapify sonrası: {data}")
print(f"Minimum: {data[0]}")

# ===== EN BÜYÜK/KÜÇÜK N ELEMAN =====
print("\n=== NLARGEST / NSMALLEST ===")

numbers = [64, 34, 25, 12, 22, 11, 90, 45, 78, 55]
print(f"Liste: {numbers}")

# nlargest(n, iterable) - En büyük n eleman
# nsmallest(n, iterable) - En küçük n eleman
print(f"En büyük 3: {heapq.nlargest(3, numbers)}")
print(f"En küçük 3: {heapq.nsmallest(3, numbers)}")

# ===== HEAPPUSHPOP ve HEAPREPLACE =====
print("\n=== ÖZEL FONKSİYONLAR ===")

heap = [5, 10, 15, 20]
heapq.heapify(heap)
print(f"Başlangıç heap: {heap}")

# heappushpop(heap, item): Önce ekle, sonra en küçüğü çıkar
# push ve pop'tan daha verimli!
result = heapq.heappushpop(heap, 3)
print(f"heappushpop(3): çıkan={result}, heap={heap}")

# heapreplace(heap, item): Önce en küçüğü çıkar, sonra ekle
result = heapq.heapreplace(heap, 25)
print(f"heapreplace(25): çıkan={result}, heap={heap}")
Çıktı bekleniyor...

🔺 Max Heap Nasıl Yapılır?

Python heapq sadece min heap destekler. Max heap için negatif değerler trick'i kullanılır!

Max Heap Wrapper Sınıfı
import heapq

class MaxHeap:
    """
    Python heapq min heap olduğu için,
    değerleri negatif yaparak max heap simüle ederiz.

    Mantık: -90 < -85 < -70 olduğu için,
    min heap'te -90 en üstte olur = aslında 90 en büyük!
    """

    def __init__(self):
        self._heap = []  # Alt çizgi: "private" değişken

    def push(self, val):
        """Değer ekle"""
        # Negatifini ekle
        heapq.heappush(self._heap, -val)

    def pop(self):
        """En büyük değeri çıkar ve döndür"""
        # Negatifi çıkar, pozitife çevir
        return -heapq.heappop(self._heap)

    def peek(self):
        """En büyük değere bak (silmeden)"""
        if self._heap:
            return -self._heap[0]
        return None

    def __len__(self):
        """len() fonksiyonu için"""
        return len(self._heap)

    def __bool__(self):
        """bool() dönüşümü için - boş mu?"""
        return len(self._heap) > 0

# Test
print("=== MAX HEAP KULLANIMI ===\n")

max_heap = MaxHeap()

# Eklemeler
for val in [35, 10, 90, 25, 5, 70]:
    max_heap.push(val)
    print(f"Push({val}) → En büyük: {max_heap.peek()}")

print(f"\nHeap boyutu: {len(max_heap)}")

# Çıkarmalar
print("\nÇıkarmalar (büyükten küçüğe):")
while max_heap:  # __bool__ sayesinde çalışır
    print(f"  Pop: {max_heap.pop()}")
Çıktı bekleniyor...

📝 Python Syntax Açıklamaları

_ prefix (_heap): Python'da alt çizgi ile başlayan değişkenler "private" kabul edilir. Dışarıdan erişilmemesi gerektiğini belirtir (ama engellemez).
__len__(self): Özel method. len(obj) çağrıldığında bu method çalışır.
__bool__(self): Özel method. if obj: veya bool(obj) çağrıldığında bu method çalışır.

🏆 Heap Sort Algoritması

Heap Sort Implementasyonu
import heapq

def heap_sort(arr):
    """
    Heap Sort - O(n log n) garantili

    Algoritma:
    1. Diziyi heap'e çevir - O(n)
    2. Tek tek çıkar (her çıkarma O(log n)) - O(n log n)

    Toplam: O(n log n)
    """
    # 1. Heap oluştur (in-place)
    heap = arr.copy()
    heapq.heapify(heap)  # O(n)

    # 2. Tek tek çıkar
    result = []
    while heap:
        result.append(heapq.heappop(heap))  # O(log n)

    return result

# Test
original = [64, 34, 25, 12, 22, 11, 90, 45]
print(f"Orijinal: {original}")

sorted_arr = heap_sort(original)
print(f"Sıralı:   {sorted_arr}")

# ===== HEAP SORT ÖZELLİKLERİ =====
print("\n" + "="*50)
print("HEAP SORT KARŞILAŞTIRMA")
print("="*50)

comparisons = [
    ("Zaman (her durum)", "O(n log n)", "Quick Sort en kötü O(n²)"),
    ("Alan", "O(1) in-place", "Merge Sort O(n) ek alan"),
    ("Stabilite", "❌ Stabil değil", "Merge Sort stabil"),
    ("Cache", "❌ Cache-unfriendly", "Quick Sort cache-friendly"),
    ("Pratikte", "Genelde yavaş", "Quick Sort tercih edilir"),
]

for ozellik, heap, karsilastirma in comparisons:
    print(f"\n{ozellik}:")
    print(f"  Heap Sort: {heap}")
    print(f"  Not: {karsilastirma}")
Çıktı bekleniyor...

📚 Terimler Açıklaması

In-place: Ek bellek kullanmadan, mevcut dizi üzerinde işlem yapma. Heap sort gerçek implementasyonda O(1) ek alan kullanır.
Cache-friendly: CPU önbelleği, ardışık bellek adreslerini önceden yükler. Heap'te parent-child atlama olduğu için cache miss (ıskalama) fazla olur → yavaş.
Stabil sıralama: Eşit değerli elemanların orijinal sırası korunur. Heap sort'ta elemanlar yer değiştirirken bu sıra bozulabilir.

🧩 Pratik Örnekler

Örnek 1: K. En Büyük Eleman

Kth Largest Element
import heapq

def kth_largest(nums, k):
    """
    Dizideki k. en büyük elemanı bul.

    Yöntem 1: Sırala ve k. elemanı al - O(n log n)
    Yöntem 2: Min heap ile - O(n log k)

    Min heap kullanarak: k elemanlı heap tut,
    yeni eleman heap'in minimumundan büyükse değiştir.
    Sonunda heap'in kökü k. en büyük!
    """
    # k elemanlı min heap oluştur
    min_heap = nums[:k]
    heapq.heapify(min_heap)

    # Kalan elemanları işle
    for num in nums[k:]:
        if num > min_heap[0]:  # Heap'in minimumundan büyükse
            heapq.heapreplace(min_heap, num)  # Değiştir

    # Heap'in kökü k. en büyük
    return min_heap[0]

# Test
nums = [3, 2, 1, 5, 6, 4]
k = 2
print(f"Dizi: {nums}")
print(f"{k}. en büyük: {kth_largest(nums, k)}")  # 5

nums2 = [3, 2, 3, 1, 2, 4, 5, 5, 6]
k2 = 4
print(f"\nDizi: {nums2}")
print(f"{k2}. en büyük: {kth_largest(nums2, k2)}")  # 4
Çıktı bekleniyor...

Örnek 2: K Sıralı Listeyi Birleştirme

Merge K Sorted Lists
import heapq

def merge_k_lists(lists):
    """
    K adet sıralı listeyi tek sıralı listede birleştir.

    Heap kullanarak O(n log k):
    - Her listeden bir eleman heap'e
    - Her seferinde minimum çıkar, o listeden sonrakini ekle
    """
    result = []

    # (değer, liste_indeksi, eleman_indeksi) tuple'ları
    # Heap tuple'ları ilk elemana göre karşılaştırır
    min_heap = []

    # Her listeden ilk elemanı ekle
    for i, lst in enumerate(lists):
        if lst:  # Boş liste kontrolü
            heapq.heappush(min_heap, (lst[0], i, 0))

    while min_heap:
        val, list_idx, elem_idx = heapq.heappop(min_heap)
        result.append(val)

        # O listeden sonraki eleman varsa ekle
        if elem_idx + 1 < len(lists[list_idx]):
            next_val = lists[list_idx][elem_idx + 1]
            heapq.heappush(min_heap, (next_val, list_idx, elem_idx + 1))

    return result

# Test
lists = [
    [1, 4, 5],
    [1, 3, 4],
    [2, 6]
]

print("Giriş listeleri:")
for i, lst in enumerate(lists):
    print(f"  Liste {i+1}: {lst}")

merged = merge_k_lists(lists)
print(f"\nBirleşik: {merged}")
Çıktı bekleniyor...

🌍 Gerçek Hayat Kullanım Alanları

⭐ Priority Queue (İşletim Sistemleri)

İşlemci zamanlaması: Yüksek öncelikli process'ler önce çalışır. Linux kernel heap kullanır.

🗺️ Dijkstra Algoritması

En kısa yol bulma: Her adımda en küçük mesafeli düğümü seç. Min heap ile O(E log V). Detaylar için Graf: Dijkstra bölümüne bakın.

📈 Running Median

Akan veride medyan: İki heap (max + min) kullanarak O(log n) güncelleme.

🔧 Huffman Coding

Veri sıkıştırma: En az frekansa sahip iki düğümü birleştir. Min heap ile verimli.

🎮 Event-Driven Simulation

Oyun motorları: Olayları zamana göre sırala, en yakın olayı işle.

📊 Top-K Problemleri

En çok aranan kelimeler, en yüksek puanlı oyuncular, trending topics...

🆚 Heap vs Diğer Veri Yapıları

İşlem Sırasız Dizi Sıralı Dizi BST (Dengeli) Heap
Find Min/Max (Min/Max Bulma) O(n) O(1) O(log n) O(1)
Insert (Ekleme) O(1) O(n) O(log n) O(log n)
Extract Min/Max (Min/Max Çıkarma) O(n) O(1)* O(log n) O(log n)
Search Arbitrary (Rastgele Arama) O(n) O(log n) O(log n) O(n)
Build from n elements (n Elemandan Oluşturma) O(n) O(n log n) O(n log n) O(n)

* Sondan çıkarma O(1), baştan çıkarma O(n)

📋 Özet

✅ Heap Ne Zaman Kullanılır?

  • Priority Queue: Öncelikli işlem sırası gerektiğinde
  • Top-K: En büyük/küçük K eleman gerektiğinde
  • Medyan: Akan veride medyan takibi
  • Graf Algoritmaları: Dijkstra, Prim (MST) algoritmaları

❌ Heap Ne Zaman Kullanılmaz?

  • Rastgele arama: Belirli bir elemanı bulmak O(n)
  • Sıralı gezinme: Elemanları sırayla gezmek için uygun değil
  • Range query: "10-20 arası elemanlar" gibi sorgular