⭐ 3.7: Priority Queue (Öncelikli Kuyruk)

Heap Veri Yapısı ve heapq Modülü

📚 Bu Konuyu Anlamak İçin

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.

🤔 Priority Queue Nedir?

Motivasyon: Neden Normal Queue Yetmez?

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.

Normal Queue (FIFO)

1️⃣ İlk gelen → İlk çıkar

Priority Queue

En öncelikli → İlk çıkar

Gerçek Hayat Örnekleri

🏥
Acil Servis

Kalp krizi > Kırık kol > Soğuk algınlığı

✈️
Havalimanı

First Class > Business > Economy

💻
İşletim Sistemi

Sistem süreci > Kullanıcı uygulaması

🌳 Heap Veri Yapısı Nedir?

Heap'i Anlamak İçin Bir Benzetme

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.

1 3 2 7 6 5 4

Min-Heap örneği: En küçük değer (1) tepede

Min-Heap

En küçük değerli eleman tepede. Her ebeveyn, çocuklarından küçük veya eşit.

Örnek: [1, 3, 2, 7, 6, 5, 4]

Max-Heap

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]

Neden Heap Kullanılır?

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.

Zaman Karmaşıklığı Karşılaştırması

Operasyon Heap Sıralı Liste Sırasız Liste
Insert (Ekleme) O(log n) O(n) O(1)
Extract Min/Max O(log n) O(1) O(n)
Peek (Bakma) O(1) O(1) O(n)

🐍 Python heapq Modülü

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

Python Kodu
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]}")

💻 Priority Queue Sınıfı

Neden Wrapper Sınıf Yazıyoruz?

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.

Python Kodu
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'nın En Kısa Yol Algoritması

Dijkstra Nedir?

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.

4 2 1 3 5 2 1 A B C D E F BAŞLANGIÇ HEDEF Kenar sayıları = mesafe

Ağırlıklı graf örneği: A'dan F'ye en kısa yolu bulmak istiyoruz

Python Kodu
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)}")