🧩 3.4: Queue Algoritmaları ve Simülasyonlar

Gerçek Hayat Problemleri ve Çözümleri

📚 Queue Kullanım Alanları

🎮

Oyun Geliştirme

Oyuncu hareketleri, input buffer, event sistemi

🌐

Web Sunucuları

HTTP istek kuyruğu, load balancing

📱

Mesajlaşma

Message queue, async işlemler

🔄

Task Scheduling

CPU process scheduling, cron jobs

1. Sıcak Patates Oyunu (Josephus Problemi)

🥔 Sıcak Patates Nedir?

Çocuk oyunu: Herkes daire şeklinde oturur, bir patates (veya top) elden ele geçirilir. Müzik durduğunda elinde patates kalan oyundan çıkar!

📜 Tarihsel Arka Plan: Josephus Problemi

MS 67, Yahudi-Roma Savaşı: Tarihçi Josephus ve 40 asker Romalılar tarafından kuşatıldı. Teslim olmak yerine intihar etmeye karar verdiler. Daire şeklinde durup her 3. kişi öldürülecekti. Josephus matematik kullanarak kendini ve arkadaşını son iki kişi olarak konumlandırdı ve hayatta kaldılar!

🔄 Queue ile Nasıl Çözülür?

  1. Başlangıç: Tüm oyuncuları queue'ya ekle
  2. Her turda: k kez dequeue yap ve hemen enqueue yap (elden ele geçir)
  3. Eleme: k. kişiyi dequeue yap ama geri ekleme (elendi!)
  4. Tekrar: Tek kişi kalana kadar devam et
🐍 Python Kodu
from collections import deque

def sicak_patates(oyuncular, k):
    """
    Sıcak Patates / Josephus Problemi

    Args:
        oyuncular: Oyuncu isimleri listesi
        k: Kaç tur geçtikten sonra eleme olacak

    Returns:
        Kazanan oyuncu
    """
    kuyruk = deque(oyuncular)
    tur = 1

    print("🎮 SICAK PATATES OYUNU BAŞLIYOR!")
    print(f"📋 Oyuncular: {list(kuyruk)}")
    print(f"🔢 Her {k}. kişi elenecek\n")
    print("=" * 50)

    while len(kuyruk) > 1:
        print(f"\n🔄 Tur {tur}: Patates elden ele...")

        # k-1 kez patatesi geçir (dequeue + enqueue)
        for i in range(k - 1):
            gecen = kuyruk.popleft()
            kuyruk.append(gecen)
            print(f"   {i+1}. geçiş: {gecen} → sona gitti")

        # k. kişi eleniyor
        elenen = kuyruk.popleft()
        print(f"\n   ❌ {elenen} ELENDİ! (Patates onda kaldı)")
        print(f"   📋 Kalanlar: {list(kuyruk)}")
        tur += 1

    kazanan = kuyruk[0]
    print("\n" + "=" * 50)
    print(f"🏆 KAZANAN: {kazanan}")
    return kazanan

# Oyunu başlat!
oyuncular = ["Ali", "Ayşe", "Mehmet", "Fatma", "Ahmet"]
sicak_patates(oyuncular, k=3)

2. Yazıcı Kuyruğu (Printer Spooler)

🖨️ Printer Spooler Nedir?

SPOOL = Simultaneous Peripheral Operations On-Line

Birden fazla kullanıcı aynı anda yazdırma isteği gönderdiğinde, yazıcı bunları sırayla işler. İlk gelen istek ilk işlenir (FIFO) - tam olarak Queue veri yapısının davranışı!

🤔 Neden Queue Kullanılır?

  • Adalet: İlk gelen ilk yazdırılır, kimse beklemez
  • Sıra Garantisi: Belgeler karışmaz, doğru sırada çıkar
  • Kaynak Yönetimi: Yazıcı tek işlem yapar, diğerleri bekler
  • Asenkron İşlem: Kullanıcı beklemeden devam edebilir
🐍 Python Kodu
from collections import deque
import random

class YaziciKuyrugu:
    """Yazıcı Spooler Simülasyonu"""

    def __init__(self, yazici_adi):
        self.yazici_adi = yazici_adi
        self.kuyruk = deque()
        self.toplam_is = 0

    def yazdir_ekle(self, kullanici, belge, sayfa_sayisi):
        """Yazdırma işi ekle"""
        is_bilgisi = {
            "id": self.toplam_is + 1,
            "kullanici": kullanici,
            "belge": belge,
            "sayfa": sayfa_sayisi
        }
        self.kuyruk.append(is_bilgisi)
        self.toplam_is += 1
        print(f"📥 Eklendi: #{is_bilgisi['id']} - {kullanici}: {belge} ({sayfa_sayisi} sayfa)")

    def yazdir(self):
        """Sıradaki işi yazdır"""
        if not self.kuyruk:
            print("❌ Kuyruk boş, yazdırılacak iş yok!")
            return None

        is_bilgisi = self.kuyruk.popleft()
        print(f"\n🖨️ YAZDIRILIYOR...")
        print(f"   📄 İş #{is_bilgisi['id']}: {is_bilgisi['belge']}")
        print(f"   👤 Kullanıcı: {is_bilgisi['kullanici']}")
        print(f"   📑 Sayfa: {is_bilgisi['sayfa']} sayfa")
        print(f"   ✅ Tamamlandı!")
        return is_bilgisi

    def kuyruk_durumu(self):
        """Kuyruktaki işleri göster"""
        print(f"\n📊 {self.yazici_adi} - Kuyruk Durumu:")
        if not self.kuyruk:
            print("   (Boş)")
        else:
            for i, is_bilgisi in enumerate(self.kuyruk, 1):
                print(f"   {i}. #{is_bilgisi['id']} {is_bilgisi['kullanici']}: {is_bilgisi['belge']}")

# Simülasyon
print("=" * 50)
print("🖨️ OFİS YAZICI SİMÜLASYONU")
print("=" * 50)

yazici = YaziciKuyrugu("HP LaserJet")

# Çalışanlar yazdırma işi gönderiyor
yazici.yazdir_ekle("Ahmet", "Rapor.pdf", 15)
yazici.yazdir_ekle("Ayşe", "Sunum.pptx", 8)
yazici.yazdir_ekle("Mehmet", "Fatura.xlsx", 2)
yazici.yazdir_ekle("Fatma", "Sözleşme.docx", 25)

yazici.kuyruk_durumu()

print("\n" + "=" * 50)
print("📤 YAZDIRMA BAŞLIYOR (FIFO Sırası)")
print("=" * 50)

# Tüm işleri yazdır
while yazici.kuyruk:
    yazici.yazdir()
    yazici.kuyruk_durumu()

3. Sliding Window Maximum (Kayan Pencere)

🎯 Problem Ne Diyor?

Bir dizi üzerinde k boyutlu bir pencere kaydır. Her konumda pencere içindeki en büyük sayıyı bul.

🔑 3 Basit Kural (Ezberle!)

1️⃣
Pencere dışını sil

Baştaki indeks pencere dışına çıktıysa → popleft()

2️⃣
Küçükleri kov

Yeni gelen > sondaki → pop() (döngüyle)

3️⃣
Ekle ve max al

Yeni indeksi sona ekle. Baştaki = max!

📊 Örnek: dizi = [1, 3, -1, -3, 5, 3, 6, 7], k = 3

Pencere [1, 3, -1] [3, -1, -3] [-1, -3, 5] [-3, 5, 3] [5, 3, 6] [3, 6, 7]
Max 3 3 5 5 6 7

Sonuç: [3, 3, 5, 5, 6, 7]

🐍 Python Kodu
from collections import deque

def sliding_window_max(nums, k):
    """
    Kayan Pencere Maksimum Problemi

    Deque'de SADECE indeksleri tutuyoruz (değerleri değil!)
    Deque her zaman azalan sırada kalır (monotonic decreasing)

    Zaman: O(n) - her eleman en fazla 1 kez eklenir, 1 kez silinir
    Alan: O(k) - deque en fazla k eleman tutar
    """
    if not nums or k == 0:
        return []

    dq = deque()  # İndeksleri tutar
    sonuc = []

    print(f"📊 Dizi: {nums}")
    print(f"📏 Pencere boyutu: k = {k}")
    print("=" * 60)

    for i in range(len(nums)):
        print(f"\n🔄 Adım {i+1}: nums[{i}] = {nums[i]}")

        # KURAL 1: Pencere dışındakileri sil
        if dq and dq[0] < i - k + 1:
            eski = dq.popleft()
            print(f"   ❌ Kural 1: indeks {eski} pencere dışına çıktı, silindi")

        # KURAL 2: Yeni gelen daha büyükse, küçükleri kov
        while dq and nums[dq[-1]] < nums[i]:
            kovulan = dq.pop()
            print(f"   🚫 Kural 2: nums[{kovulan}]={nums[kovulan]} < {nums[i]}, kovuldu")

        # KURAL 3: Yeni indeksi ekle
        dq.append(i)
        print(f"   ➕ Kural 3: indeks {i} eklendi → deque: {list(dq)}")

        # Pencere dolduğunda max'ı kaydet
        if i >= k - 1:
            max_val = nums[dq[0]]
            sonuc.append(max_val)
            pencere = nums[i-k+1:i+1]
            print(f"   ✅ Pencere {pencere} → Max: {max_val}")

    print("\n" + "=" * 60)
    print(f"🎯 SONUÇ: {sonuc}")
    return sonuc

# Test
nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
sliding_window_max(nums, k)

4. CPU Task Scheduler (Round Robin)

⚙️ Round Robin Scheduling Nedir?

İşletim sistemlerinin en temel CPU zamanlama algoritmasıdır. Her işleme (process) eşit bir zaman dilimi (time quantum) verilir.

İşlem zaman dilimi içinde bitmezse, kuyruğun sonuna gönderilir ve sıradaki işleme geçilir. Bu sayede tüm işlemler adil bir şekilde CPU zamanı alır.

✅ Avantajları

  • Adalet: Her işlem eşit CPU zamanı alır
  • Starvation Yok: Hiçbir işlem sonsuza kadar beklemez
  • Düşük Gecikme: Kısa işlemler hızlıca tamamlanır
  • Preemptive: İşlemler zorla durdurulabilir
🐍 Python Kodu
from collections import deque

class RoundRobinScheduler:
    """
    Round Robin CPU Scheduler Simülasyonu

    Her işleme eşit zaman dilimi (time quantum) verilir.
    İşlem bitmezse kuyruğun sonuna gönderilir.
    """

    def __init__(self, time_quantum):
        self.time_quantum = time_quantum
        self.ready_queue = deque()
        self.current_time = 0

    def add_process(self, name, burst_time):
        """Yeni işlem ekle"""
        process = {"name": name, "remaining": burst_time, "original": burst_time}
        self.ready_queue.append(process)
        print(f"📥 Eklendi: {name} (Toplam: {burst_time} birim)")

    def run(self):
        """Tüm işlemleri çalıştır"""
        print(f"\n{'=' * 60}")
        print(f"⚙️ ROUND ROBIN SCHEDULER")
        print(f"⏱️ Time Quantum: {self.time_quantum} birim")
        print(f"{'=' * 60}")

        completed = []

        while self.ready_queue:
            process = self.ready_queue.popleft()
            name = process["name"]
            remaining = process["remaining"]

            # Ne kadar çalışacak?
            run_time = min(self.time_quantum, remaining)

            print(f"\n🔄 T={self.current_time}: {name} çalışıyor ({run_time} birim)")

            self.current_time += run_time
            process["remaining"] -= run_time

            if process["remaining"] > 0:
                # İşlem bitmedi, kuyruğun sonuna ekle
                self.ready_queue.append(process)
                print(f"   ⏸️ {name} durdu, kalan: {process['remaining']} birim → Kuyruğa geri")
            else:
                # İşlem tamamlandı
                turnaround = self.current_time
                completed.append({"name": name, "turnaround": turnaround})
                print(f"   ✅ {name} TAMAMLANDI! (Turnaround: {turnaround} birim)")

            # Kuyruk durumu
            queue_names = [p["name"] for p in self.ready_queue]
            print(f"   📋 Kuyruk: {queue_names if queue_names else '(Boş)'}")

        # Özet
        print(f"\n{'=' * 60}")
        print("📊 ÖZET")
        print(f"{'=' * 60}")
        avg_turnaround = sum(p["turnaround"] for p in completed) / len(completed)
        for p in completed:
            print(f"   {p['name']}: Turnaround = {p['turnaround']} birim")
        print(f"\n   📈 Ortalama Turnaround: {avg_turnaround:.1f} birim")

# Simülasyon
scheduler = RoundRobinScheduler(time_quantum=4)

# İşlemler ekle
scheduler.add_process("P1", 10)  # 10 birim CPU zamanı gerekiyor
scheduler.add_process("P2", 5)   # 5 birim
scheduler.add_process("P3", 8)   # 8 birim

# Çalıştır
scheduler.run()

5. Rate Limiter (Hız Sınırlayıcı)

🚦 Rate Limiter Nedir?

API'lere gelen istekleri sınırlandırır. Örneğin: "Dakikada en fazla 100 istek" kuralı.

DDoS saldırılarını önler, sunucu kaynaklarını korur, adil kullanım sağlar.

📐 Sliding Window Yaklaşımı

Her istek geldiğinde:

  1. Zaman damgasını kaydet (queue'ya ekle)
  2. Pencere dışındaki eski istekleri sil (popleft)
  3. Kuyruktaki istek sayısını kontrol et
  4. Limit aşıldıysa → REJECT, değilse → ALLOW
🐍 Python Kodu
from collections import deque
import time

class RateLimiter:
    """
    Sliding Window Rate Limiter

    Belirli bir zaman penceresinde maksimum istek sayısını sınırlar.
    Queue'da zaman damgalarını tutarak eski istekleri temizler.
    """

    def __init__(self, max_requests, window_seconds):
        """
        Args:
            max_requests: Pencere içinde izin verilen max istek
            window_seconds: Pencere süresi (saniye)
        """
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.requests = deque()  # Zaman damgalarını tutar

    def allow_request(self, current_time):
        """
        İsteğe izin verilip verilmeyeceğini kontrol et

        Returns:
            True: İzin verildi
            False: Rate limit aşıldı
        """
        # Pencere dışındaki eski istekleri temizle
        window_start = current_time - self.window_seconds
        while self.requests and self.requests[0] < window_start:
            self.requests.popleft()

        # Limit kontrolü
        if len(self.requests) < self.max_requests:
            self.requests.append(current_time)
            return True
        else:
            return False

    def get_status(self):
        """Mevcut durumu göster"""
        return f"{len(self.requests)}/{self.max_requests}"

# Simülasyon
print("=" * 60)
print("🚦 RATE LIMITER SİMÜLASYONU")
print("📋 Kural: 10 saniyede en fazla 5 istek")
print("=" * 60)

limiter = RateLimiter(max_requests=5, window_seconds=10)

# Simülasyon senaryosu
test_times = [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 20, 21]

for t in test_times:
    allowed = limiter.allow_request(t)
    status = limiter.get_status()

    if allowed:
        print(f"T={t:2d}s → ✅ İZİN VERİLDİ  [{status}]")
    else:
        print(f"T={t:2d}s → ❌ REDDEDİLDİ   [{status}] (Rate limit aşıldı!)")

print("\n" + "=" * 60)
print("💡 Not: T=5, 6, 7, 8'de limit aşıldı çünkü son 10 saniyede")
print("   zaten 5 istek yapılmıştı. T=11'de pencere kaydı ve yer açıldı.")