🔄 4.4: Circular Linked List

Dairesel Bağlı Liste - Sonsuz Döngü

🤔 Circular Linked List Nedir?

Dairesel Bağlı Listede son node'un next işaretçisi NULL yerine ilk node'a (head) işaret eder. Bu sayede liste bir halka oluşturur ve sonsuza kadar dönebilirsiniz.

HEAD A B C D D.next → A (NULL değil!) 🔄
A → B → C → D → A → B → C → D → A → ... ∞

❓ Neden Single/Double Linked List Yetmiyor?

Bu soruyu cevaplamak için önce şu senaryoyu düşünelim: Bir müzik çalar uygulaması yazıyorsunuz ve kullanıcı "tekrar modu"nu (repeat) açtığında son şarkı bitince otomatik olarak ilk şarkıya dönmesini istiyorsunuz. Ya da bir oyun yapıyorsunuz ve 4 oyuncu sırayla oynuyor — 4. oyuncudan sonra tekrar 1. oyuncuya geçmesi gerekiyor.

❌ Normal Linked List ile Bu Senaryoyu Yazmaya Çalışalım

Diyelim ki 4 şarkılık bir playlist'imiz var: A → B → C → D → NULL

Şarkıları sırayla çalmak için şöyle bir kod yazarız:

current = head  # A'dan başla
while current != None:
    play(current.song)    # Şarkıyı çal
    current = current.next  # Sonraki şarkıya geç

Bu kod D şarkısını çaldıktan sonra current = None olur ve döngü biter. Peki ya kullanıcı "tekrar modu" açtıysa? O zaman başa dönmemiz gerekiyor:

current = head
while repeat_mode:
    play(current.song)
    current = current.next

    # ⚠️ SORUN: Liste bitti mi kontrol et!
    if current == None:
        current = head  # Başa dön - ama HEAD'i ayrıca saklamak zorundayız!

Burada birkaç sorun var:

Birincisi, her adımda "liste bitti mi?" diye kontrol etmek zorundayız. Bu ekstra bir if kontrolü demek ve kodun karmaşıklığını artırıyor. İkincisi, head değişkenini ayrıca saklamak ve erişilebilir tutmak zorundayız. Eğer fonksiyonlar arasında geçiş yapıyorsak veya sadece current pointer'ını parametre olarak geçiriyorsak, head'e nasıl ulaşacağız? Üçüncüsü, bu yaklaşım hata yapmaya çok açık. Geliştirici if current == None kontrolünü unutursa program çöker.

✅ Circular Linked List ile Aynı Senaryo

Şimdi aynı playlist'i Circular Linked List olarak düşünelim: A → B → C → D → A → B → ...

Son node (D), NULL yerine ilk node'a (A) işaret ediyor. Kod şöyle basitleşiyor:

current = head
while repeat_mode:
    play(current.song)
    current = current.next  # Otomatik olarak başa döner!

Fark ettiniz mi?

Hiçbir if kontrolü yok! D'den sonra current.next zaten A'yı gösteriyor, dolayısıyla otomatik olarak başa dönüyoruz. HEAD'i ayrıca saklamaya gerek yok çünkü liste hiç bitmiyor — sonsuza kadar dönüyor. Kod çok daha temiz, okunabilir ve hata yapma ihtimali çok daha düşük.

Bu, Circular Linked List'in temel avantajı: döngüsel işlemleri doğal olarak modelliyor. NULL kontrolü yapmak yerine, veri yapısının kendisi döngüyü sağlıyor.

💻 Gerçek Dünya Örneği: Round Robin CPU Scheduling

İşletim sistemleri, birden fazla programın (process) aynı anda çalışıyormuş gibi görünmesini sağlamak için Round Robin algoritmasını kullanır. Her process'e sırayla küçük bir zaman dilimi (time quantum) verilir ve süre dolunca sıradaki process'e geçilir.

Diyelim ki 3 process var: P1, P2, P3. Her birine 10ms zaman verilecek:

P1(10ms) → P2(10ms) → P3(10ms) → P1(10ms) → P2(10ms) → P3(10ms) → ...

Bu tam olarak circular bir yapı! Eğer normal linked list kullansaydık, P3'ten sonra "liste bitti, başa dön" mantığı yazmak zorunda kalacaktık. Ama circular linked list ile scheduler kodu şöyle basit oluyor:

current_process = process_list.head
while True:
    run_for(current_process, time_quantum=10)  # 10ms çalıştır
    current_process = current_process.next      # Sonraki process (otomatik döngü!)

Hiçbir process sonsuza kadar beklemez, hepsi eşit şekilde CPU zamanı alır ve kod son derece temiz kalır.

Durum Normal LL Circular LL
Son elemandan sonra ne olur? NULL → Program durur veya crash İlk elemana döner → Devam eder
Başa dönmek için HEAD'i ayrıca sakla + if kontrolü Hiçbir şey gerekmez
Sonsuz döngü için Manuel reset kodu gerekli Doğal olarak sağlanır
Kod karmaşıklığı Ekstra kontroller, hata riski ↑ Temiz, minimal, hata riski ↓

🎮 İnteraktif Dairesel Liste

🔄
0 tur

📋 Circular Liste Türleri

🔵 Circular Singly

A → B → C → ↩ A

Sadece ileri yönde döner.
Tek pointer (next)

🟣 Circular Doubly

A ⟷ B ⟷ C

İki yönde de döner.
İki pointer (prev, next)

💻 Python Implementasyonu

Python Kodu
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class CircularLinkedList:
    """Dairesel Tek Yönlü Bağlı Liste"""
    
    def __init__(self):
        self.head = None
        self.size = 0
    
    def append(self, data):
        """Sona ekle"""
        new_node = Node(data)
        
        if not self.head:
            self.head = new_node
            new_node.next = self.head  # Kendine döner
        else:
            # Son node'u bul
            current = self.head
            while current.next != self.head:
                current = current.next
            current.next = new_node
            new_node.next = self.head  # Başa bağla
        
        self.size += 1
        print(f"✅ Eklendi: {data}")
    
    def prepend(self, data):
        """Başa ekle"""
        new_node = Node(data)
        
        if not self.head:
            self.head = new_node
            new_node.next = self.head
        else:
            # Son node'u bul
            current = self.head
            while current.next != self.head:
                current = current.next
            
            new_node.next = self.head
            current.next = new_node  # Son node yeni başı göstersin
            self.head = new_node
        
        self.size += 1
        print(f"✅ Başa eklendi: {data}")
    
    def delete(self, data):
        """Değeri sil"""
        if not self.head:
            return
        
        # Silinecek head ise
        if self.head.data == data:
            if self.head.next == self.head:  # Tek eleman
                self.head = None
            else:
                # Son node'u bul
                current = self.head
                while current.next != self.head:
                    current = current.next
                current.next = self.head.next
                self.head = self.head.next
            self.size -= 1
            print(f"🗑️ Silindi: {data}")
            return
        
        # Ara ve sil
        current = self.head
        while current.next != self.head:
            if current.next.data == data:
                current.next = current.next.next
                self.size -= 1
                print(f"🗑️ Silindi: {data}")
                return
            current = current.next
    
    def traverse(self, rounds=1):
        """Listeyi dolaş (kaç tur?)"""
        if not self.head:
            print("Liste boş!")
            return
        
        print(f"🔄 {rounds} tur dolaşılıyor:")
        current = self.head
        count = 0
        
        for _ in range(rounds * self.size):
            print(f"  → {current.data}", end="")
            current = current.next
            count += 1
            
            if count % self.size == 0:
                print(" (tur tamamlandı)")
    
    def display(self):
        """Listeyi göster"""
        if not self.head:
            print("Liste: [BOŞ]")
            return
        
        elements = []
        current = self.head
        while True:
            elements.append(str(current.data))
            current = current.next
            if current == self.head:
                break
        
        print(f"Liste: {' → '.join(elements)} → [HEAD'e döner] 🔄")

# Test
print("🔄 Circular Linked List Demo\n")
cll = CircularLinkedList()

cll.append("A")
cll.append("B")
cll.append("C")
cll.append("D")
cll.display()

print()
cll.traverse(2)  # 2 tur

print()
cll.delete("B")
cll.display()

print()
cll.prepend("X")
cll.display()
Çıktı bekleniyor...

🎯 Kullanım Alanları

🎵

Müzik Playlist

Son şarkıdan sonra başa dön (repeat mode)

Round Robin Scheduler

CPU zaman paylaşımı (işlemler sırayla)

🎮

Oyun Sırası

Oyuncular sırayla oynasın (Monopoly gibi)

🖼️

Carousel / Slider

Sonsuz dönen resim galerisi

🔥 Josephus Problemi - Circular LL ile Çözüm

📜 Tarihsel Problem (MS 67)

Josephus Flavius ve 40 Yahudi asker Romalılar tarafından kuşatıldı. Teslim olmak yerine toplu intihar kararı aldılar: Daire şeklinde durup her k. kişi öldürülecekti. Josephus matematiği kullanarak kendini doğru konuma yerleştirdi ve hayatta kaldı!

🔄 Neden Circular Linked List?

  • Daire yapısı: Son kişiden sonra otomatik başa döner
  • Silme O(1): prev.next = current.next ile anında silme
  • Doğal model: Oyuncuların daire şeklinde oturmasını tam olarak modeller
  • Queue alternatifi: Queue ile de çözülür ama Circular LL daha doğal

🎮 İnteraktif Josephus Simülasyonu

Her k. kişi elenir. Parametreleri değiştirip simülasyonu izleyin!

Simülasyonu başlatmak için "Bir Adım" veya "Otomatik" butonuna tıklayın
Python Kodu
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def josephus_circular(players, k):
    """
    Josephus Problemi - Circular Linked List ile
    Her k. kişi elenir, son kalan kazanır.
    """
    if not players:
        return None
    
    # Circular liste oluştur
    head = Node(players[0])
    current = head
    for player in players[1:]:
        current.next = Node(player)
        current = current.next
    current.next = head  # Daireyi kapat
    
    print(f"🎮 Oyun Başlıyor! k={k}")
    print(f"Oyuncular: {players}\n")
    
    # Son bir kişi kalana kadar
    prev = current  # Son node
    current = head
    
    while current.next != current:  # Tek eleman kalana kadar
        # k-1 adım ilerle
        for _ in range(k - 1):
            prev = current
            current = current.next
        
        # current'ı elen
        print(f"❌ {current.data} elendi!")
        prev.next = current.next
        current = current.next
    
    print(f"\n🏆 KAZANAN: {current.data}")
    return current.data

# Test
players = ["Ali", "Veli", "Ayşe", "Fatma", "Can", "Zeynep"]
josephus_circular(players, k=3)
Çıktı bekleniyor...

🧮 Matematiksel Formül (Bonus)

Josephus probleminin kapalı formül çözümü de vardır (k=2 için):

J(n) = 2L + 1, burada n = 2m + L ve 0 ≤ L < 2m

Örnek: n=6 için: 6 = 4 + 2 = 2² + 2, yani L=2 → J(6) = 2×2 + 1 = 5
Circular LL ile simülasyon, bu formülü anlamak ve doğrulamak için mükemmel!

🥔 Hot Potato (Sıcak Patates) - Josephus'un Eğlenceli Versiyonu

🎮 Oyun Kuralları

Çocuklar daire şeklinde oturur. Müzik çalarken bir top (veya patates) elden ele geçer. Müzik durduğunda topu elinde tutan kişi elenir. Son kalan kazanır!

Josephus ile farkı: Josephus'ta sayı sabit (her k. kişi), Hot Potato'da rastgele (müzik ne zaman durursa).

🎮 İnteraktif Sıcak Patates Simülasyonu

Müzik rastgele durur. Her turda 1-5 arası geçiş yapılır!

Patates Ali'de! "Otomatik Tur" ile rastgele sayıda geçiş yapılır.

Örnek Oyun Akışı

Ali →🥔→ Bea →🥔→ Can 💥 ELENDİ!

Müzik 3. geçişte durdu → Can elendi, oyun 4 kişiyle devam eder

Hot Potato - Python Kodu
import random

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def hot_potato(players):
    """
    Sıcak Patates Oyunu - Circular Linked List ile
    Her turda rastgele sayıda geçiş yapılır.
    """
    if not players:
        return None

    # Circular liste oluştur
    head = Node(players[0])
    current = head
    for player in players[1:]:
        current.next = Node(player)
        current = current.next
    current.next = head  # Daireyi kapat

    print("🥔 SICAK PATATES OYUNU!")
    print(f"Oyuncular: {players}\n")

    # Son bir kişi kalana kadar
    prev = current
    current = head
    round_num = 1

    while current.next != current:
        # Rastgele sayıda geçiş (müzik rastgele durur)
        passes = random.randint(1, 5)
        print(f"🎵 Tur {round_num}: Patates {passes} kez geçiyor...")

        for i in range(passes):
            print(f"   {current.data}", end="")
            prev = current
            current = current.next
            if i < passes - 1:
                print(" →", end="")

        print(f"\n🔇 Müzik durdu! {current.data} elendi!\n")

        # current'ı elen
        prev.next = current.next
        current = current.next
        round_num += 1

    print(f"🏆 KAZANAN: {current.data}")
    return current.data

# Oyunu oyna
players = ["Ali", "Bea", "Can", "Def", "Ela"]
hot_potato(players)
Çıktı bekleniyor...

📜 Josephus Problemi

  • Her k. kişi elenir (sabit)
  • Sonuç deterministik
  • Matematiksel formül var
  • Örnek: k=3 → her 3. kişi

🥔 Hot Potato

  • Müzik rastgele durur
  • Sonuç tahmin edilemez
  • Her oyun farklı sonuç
  • Örnek: 1-5 arası rastgele