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

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

📌 Heap Nedir?

Heap, tam ikili ağaç (complete binary tree) şeklinde düzenlenen ve heap özelliğini koruyan özel bir veri yapısıdır. En önemli özelliği: En büyük/küçük elemana O(1)'de erişim!

📚 Ö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

Tam İkili Ağaç (Complete Binary Tree): Tüm seviyeler tamamen dolu, son seviye soldan sağa doldurulur. Bu sayede dizi ile temsil edilebilir!
Heap Özelliği: Her parent düğüm, çocuklarından büyük (max heap) veya küçük (min heap) 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

090
185
270
340
480
530
625
710
835
975
1050
1120
1215
135
1422

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).

📈 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 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