Heap Veri Yapısı ve heapq Modülü
Priority Queue, Heap adlı veri yapısını kullanır. Heap konusu ileride Bölüm 9: Heap Veri Yapısı'nda detaylı olarak işlenecektir.
Bu sayfada Heap'in temellerini öğreneceksiniz, ancak tam anlamak için o bölümü de okumanız önerilir.
Normal Queue'da FIFO (First In, First Out) kuralı geçerlidir: ilk gelen ilk çıkar. Ancak gerçek hayatta her zaman bu kural geçerli olmaz!
Düşünün: Bir hastane acil servisinde sıra bekleyen hastalar var. Basit bir soğuk algınlığı olan hasta sıraya ilk girdi. Ardından kalp krizi geçiren bir hasta geldi. FIFO kuralına göre soğuk algınlığı olan hasta önce muayene edilmeli... Ama bu mantıklı mı?
İşte burada Priority Queue devreye girer! Elemanları giriş sırasına göre değil, öncelik değerine göre sıralar.
Priority Queue, normal kuyruktan farklı olarak elemanları öncelik sırasına göre işler. En yüksek (veya en düşük) öncelikli eleman her zaman ilk çıkar.
Kalp krizi > Kırık kol > Soğuk algınlığı
First Class > Business > Economy
Sistem süreci > Kullanıcı uygulaması
Bir piramit hayal edin. En tepede en önemli (veya en küçük/büyük değerli) eleman bulunur. Her seviyede aşağı indikçe elemanların önemi azalır.
Min-Heap örneği: En küçük değer (1) tepede
En küçük değerli eleman tepede. Her ebeveyn, çocuklarından küçük veya eşit.
Örnek: [1, 3, 2, 7, 6, 5, 4]
En büyük değerli eleman tepede. Her ebeveyn, çocuklarından büyük veya eşit.
Örnek: [9, 7, 8, 3, 4, 5, 6]
Priority Queue için Heap kullanmanın avantajı: hem ekleme hem de çıkarma işlemleri O(log n) sürede yapılır. Bu, büyük veri setlerinde bile hızlı çalışmasını sağlar.
Python'un heapq modülü Min-Heap kullanır. Yani en küçük değerli eleman her zaman ilk çıkar.
Python'da heapq modülü Min-Heap implementasyonu sağlar. Temel fonksiyonları öğrenelim:
heapq.heappush(heap, item)
Heap'e yeni eleman ekler
heapq.heappop(heap)
En küçük elemanı çıkarır ve döndürür
heap[0]
En küçük elemana bakar (çıkarmaz)
heapq.heapify(list)
Listeyi heap'e dönüştürür
import heapq
print("=" * 50)
print("🐍 Python heapq Modülü - Temel Kullanım")
print("=" * 50)
# Boş heap oluştur
heap = []
# Eleman ekleme (heappush)
print("\n📥 Eleman Ekleme (heappush):")
elemanlar = [5, 2, 8, 1, 9, 3]
for eleman in elemanlar:
heapq.heappush(heap, eleman)
print(f" heappush({eleman}) → Heap: {heap}")
# En küçük elemana bakma (peek)
print(f"\n👀 En küçük eleman (heap[0]): {heap[0]}")
# Eleman çıkarma (heappop)
print("\n📤 Eleman Çıkarma (heappop):")
print(" Min-Heap olduğu için her zaman EN KÜÇÜK eleman çıkar:")
while heap:
en_kucuk = heapq.heappop(heap)
print(f" heappop() → {en_kucuk}, Kalan: {heap}")
# Mevcut listeyi heap'e dönüştürme
print("\n🔄 Listeyi Heap'e Dönüştürme (heapify):")
liste = [7, 3, 9, 1, 5, 2]
print(f" Orijinal liste: {liste}")
heapq.heapify(liste)
print(f" heapify() sonrası: {liste}")
print(f" En küçük eleman tepede: {liste[0]}")
heapq modülü düşük seviyeli fonksiyonlar sunar. Kullanımı kolaylaştırmak ve daha anlaşılır bir API sağlamak için kendi PriorityQueue sınıfımızı yazabiliriz.
Bu yaklaşım, Queue bölümünde öğrendiğimiz wrapper pattern ile aynıdır.
import heapq
class PriorityQueue:
"""
Min-Heap tabanlı Priority Queue implementasyonu.
En düşük öncelik değerine sahip eleman ilk çıkar.
"""
def __init__(self):
self._heap = []
self._index = 0 # Aynı öncelikteki elemanlar için sıra
def push(self, item, priority):
"""
Kuyruğa yeni eleman ekler.
priority: Düşük değer = Yüksek öncelik
"""
# (öncelik, sıra_no, eleman) tuple'ı kullanıyoruz
# sıra_no aynı öncelikteki elemanları FIFO sırasında tutar
heapq.heappush(self._heap, (priority, self._index, item))
self._index += 1
def pop(self):
"""En yüksek öncelikli (en düşük priority değerli) elemanı çıkarır."""
if self.is_empty():
raise IndexError("Priority Queue boş!")
priority, _, item = heapq.heappop(self._heap)
return item, priority
def peek(self):
"""En yüksek öncelikli elemana bakar (çıkarmaz)."""
if self.is_empty():
return None
priority, _, item = self._heap[0]
return item, priority
def is_empty(self):
return len(self._heap) == 0
def __len__(self):
return len(self._heap)
# Test: Hastane Acil Servisi Simülasyonu
print("=" * 55)
print("🏥 Hastane Acil Servisi - Priority Queue Simülasyonu")
print("=" * 55)
pq = PriorityQueue()
# Hastalar geliyor (düşük öncelik değeri = daha acil)
hastalar = [
("Ayşe - Soğuk algınlığı", 5),
("Mehmet - Kalp krizi", 1),
("Fatma - Kırık kol", 3),
("Ali - Baş ağrısı", 4),
("Zeynep - Ağır yanık", 2),
]
print("\n📥 Hastalar sıraya giriyor:")
for hasta, oncelik in hastalar:
pq.push(hasta, oncelik)
print(f" Öncelik {oncelik}: {hasta}")
print(f"\n📊 Toplam hasta sayısı: {len(pq)}")
print(f"👀 Sıradaki hasta: {pq.peek()}")
print("\n" + "=" * 55)
print("🚑 Hastalar muayene ediliyor (öncelik sırasına göre):")
print("=" * 55)
siralama = 1
while not pq.is_empty():
hasta, oncelik = pq.pop()
print(f" {siralama}. Öncelik {oncelik}: {hasta}")
siralama += 1
print("\n✅ Tüm hastalar muayene edildi!")
Dijkstra algoritması, ağırlıklı graflarda en kısa yolu bulan ünlü bir algoritmadır. GPS navigasyon sistemlerinin temelini oluşturur. Detaylı açıklama için Bölüm 11.5: Dijkstra Algoritması'na bakabilirsiniz.
BFS ile farkı: BFS ağırlıksız graflarda çalışır (tüm kenarlar eşit). Dijkstra ise her kenarın farklı "maliyeti" (mesafe, süre, vb.) olduğu durumlarda kullanılır.
Priority Queue'nun rolü: Her adımda "en kısa mesafeli" düğümü seçmek için Priority Queue kullanılır.
Ağırlıklı graf örneği: A'dan F'ye en kısa yolu bulmak istiyoruz
import heapq
def dijkstra(graf, baslangic, hedef):
"""
Dijkstra'nın En Kısa Yol Algoritması
graf: {düğüm: [(komşu, mesafe), ...]} formatında
baslangic: Başlangıç düğümü
hedef: Hedef düğüm
Returns: (en_kisa_mesafe, yol)
"""
# Mesafeler: başlangıçtan her düğüme olan mesafe
mesafeler = {dugum: float('infinity') for dugum in graf}
mesafeler[baslangic] = 0
# Önceki düğümler: yolu geri takip etmek için
oncekiler = {dugum: None for dugum in graf}
# Priority Queue: (mesafe, düğüm)
pq = [(0, baslangic)]
# Ziyaret edilenler
ziyaret_edildi = set()
print(f"🚀 Dijkstra Başlıyor: {baslangic} → {hedef}")
print("=" * 50)
while pq:
# En kısa mesafeli düğümü al
mevcut_mesafe, mevcut = heapq.heappop(pq)
# Zaten ziyaret edildiyse atla
if mevcut in ziyaret_edildi:
continue
ziyaret_edildi.add(mevcut)
print(f"📍 Ziyaret: {mevcut} (mesafe: {mevcut_mesafe})")
# Hedefe ulaştık mı?
if mevcut == hedef:
# Yolu geri takip et
yol = []
dugum = hedef
while dugum:
yol.append(dugum)
dugum = oncekiler[dugum]
yol.reverse()
return mesafeler[hedef], yol
# Komşuları kontrol et
for komsu, kenar_mesafe in graf[mevcut]:
if komsu not in ziyaret_edildi:
yeni_mesafe = mevcut_mesafe + kenar_mesafe
# Daha kısa bir yol bulduk mu?
if yeni_mesafe < mesafeler[komsu]:
mesafeler[komsu] = yeni_mesafe
oncekiler[komsu] = mevcut
heapq.heappush(pq, (yeni_mesafe, komsu))
print(f" ➕ {komsu} güncellendi: mesafe = {yeni_mesafe}")
return float('infinity'), []
# Örnek graf (yukarıdaki şekil)
graf = {
'A': [('B', 4), ('C', 2)],
'B': [('A', 4), ('C', 1), ('D', 3)],
'C': [('A', 2), ('B', 1), ('E', 5)],
'D': [('B', 3), ('F', 2)],
'E': [('C', 5), ('F', 1)],
'F': [('D', 2), ('E', 1)]
}
print("📊 Graf Yapısı (düğüm → [(komşu, mesafe), ...]):")
for dugum, komsular in graf.items():
print(f" {dugum} → {komsular}")
print()
# En kısa yolu bul
mesafe, yol = dijkstra(graf, 'A', 'F')
print("\n" + "=" * 50)
print(f"✅ En kısa mesafe: {mesafe}")
print(f"🛤️ Yol: {' → '.join(yol)}")