➡️ 4.2: Singly Linked List

Tek Yönlü Bağlı Liste Implementasyonu

📋 Temel Operasyonlar

1️⃣ Başa Ekleme (prepend) O(1)

Listeye yeni bir eleman eklemek istiyoruz ve bu elemanı listenin en başına koymak istiyoruz. Aşağıdaki örnekte "X" değerini içeren yeni bir node oluşturup başa ekliyoruz.

Başlangıç durumu: A → B → NULL şeklinde 2 elemanlı bir listemiz var.

HEAD →
A
B
NULL
⬇️ prepend("X") — "X" değerini başa ekle

Sonuç: X artık listenin ilk elemanı, HEAD X'i gösteriyor.

HEAD →
X
A
B
NULL
💡 Neden O(1)? Sadece 2 işlem yeterli:
  1. new_node.next = head → Yeni node'un next pointer'ını mevcut head'e (A'ya) bağla
  2. head = new_node → HEAD pointer'ını yeni node'a (X'e) güncelle

📌 Önemli: Sıralama kritik! Önce yeni node'u bağlamalı, sonra HEAD'i güncellemeliyiz. Aksi halde eski listenin referansını kaybederiz.

2️⃣ Sona Ekleme (append) O(n)

Listeye yeni bir eleman eklemek istiyoruz ve bu elemanı listenin en sonuna koymak istiyoruz. Aşağıdaki örnekte "X" değerini içeren yeni bir node oluşturup sona ekliyoruz.

Başlangıç durumu: A → B → C → NULL şeklinde 3 elemanlı bir listemiz var. Önce son node'u (C) bulmalıyız.

HEAD →
A
B
C
← Son node (next = null)
⬇️ append("X") — "X" değerini sona ekle

Sonuç: X artık listenin son elemanı, C'nin next pointer'ı X'i gösteriyor.

HEAD →
A
B
C
X
💡 Neden O(n)? Son node'u bulmak için tüm listeyi dolaşmamız gerekir:
  1. current = head → Baştan başla (A)
  2. while current.next != null → next'i null olmayanları gez (A→B→C)
  3. current.next = new_node → Son node'un (C) next'ini yeni node'a (X) bağla

📌 İpucu: Tail pointer tutarak bu işlemi O(1) yapabilirsiniz! (Aşağıda optimizasyon bölümüne bakın)

3️⃣ Silme (delete) O(n)

Listeden bir eleman silmek istiyoruz. Aşağıdaki örnekte "B" değerini içeren node'u siliyoruz. Silme işleminin zorluğu: silinecek node'un öncesindeki node'u bulmamız gerekiyor.

Başlangıç durumu: A → B → C → NULL şeklinde 3 elemanlı listemiz var. "B" node'unu silmek istiyoruz.

HEAD →
A
B
C
← Silinecek: "B"
⬇️ delete("B") — "B" değerini sil

Sonuç: B listeden çıkarıldı, A artık doğrudan C'ye bağlı.

HEAD →
A
C
✅ A.next = C (B atlandı)
💡 Neden O(n)? Silinecek node'un öncesini bulmak için listeyi dolaşmamız gerekir:
  1. current = head → Baştan başla (A)
  2. while current.next.data != "B" → B'nin öncesini ara
  3. current.next = current.next.next → A'nın next'ini B'nin next'ine (C'ye) bağla

📌 Kritik nokta: Tek yönlü listede geri gidemeyiz! Bu yüzden silinecek node'u değil, onun öncesindeki node'u bulmalıyız. B'yi bulsak bile A'ya ulaşamayız.

⚠️ Özel durum: Eğer silinecek node HEAD ise (ilk eleman), sadece head = head.next yapmak yeterli — bu O(1)!

4️⃣ Arama (search) O(n)

Listede belirli bir değeri aramak istiyoruz. Aşağıdaki örnekte "C" değerini arıyoruz. Linked list'te rastgele erişim (random access) yoktur, bu yüzden baştan başlayıp sırayla kontrol etmeliyiz.

Arama süreci: A → B → C → D → NULL listesinde "C" değerini arıyoruz.

HEAD →
A
A≠C ❌
B
B≠C ❌
C
C=C ✅ BULUNDU!
D
🔍 search("C") → index: 2
💡 Neden O(n)? Aranan değer listenin herhangi bir yerinde olabilir:
  1. current = head, index = 0 → Baştan başla
  2. while current != null → Liste bitene kadar devam et
  3. if current.data == aranan → Eşleşme varsa index döndür
  4. current = current.next, index++ → Sonraki node'a geç

📌 En kötü durum: Aranan değer listenin sonundaysa veya listede yoksa, tüm n elemanı gezmemiz gerekir.

📌 Array ile karşılaştırma: Array'de index ile erişim O(1)'dir (arr[2]), ama linked list'te 2. elemana ulaşmak için baştan 2 adım atmamız gerekir!

💻 Tam Python Implementasyonu

Python Kodu
class Node:
    """Tek yönlü bağlı liste düğümü"""
    def __init__(self, data):
        self.data = data
        self.next = None

class SinglyLinkedList:
    """Tek Yönlü Bağlı Liste"""
    
    def __init__(self):
        self.head = None
        self.size = 0
    
    def prepend(self, data):
        """Başa ekle - O(1)"""
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node
        self.size += 1
        print(f"✅ Başa eklendi: {data}")
    
    def append(self, data):
        """Sona ekle - O(n)"""
        new_node = Node(data)
        
        if not self.head:
            self.head = new_node
        else:
            current = self.head
            while current.next:  # Son node'u bul
                current = current.next
            current.next = new_node
        
        self.size += 1
        print(f"✅ Sona eklendi: {data}")
    
    def delete(self, data):
        """Belirli değeri sil - O(n)"""
        if not self.head:
            print("❌ Liste boş!")
            return
        
        # Silinecek head ise
        if self.head.data == data:
            self.head = self.head.next
            self.size -= 1
            print(f"🗑️ Silindi: {data}")
            return
        
        # Ara ve sil
        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                self.size -= 1
                print(f"🗑️ Silindi: {data}")
                return
            current = current.next
        
        print(f"❌ Bulunamadı: {data}")
    
    def search(self, data):
        """Değeri ara - O(n)"""
        current = self.head
        index = 0
        while current:
            if current.data == data:
                print(f"🔍 Bulundu: {data} (index: {index})")
                return index
            current = current.next
            index += 1
        print(f"🔍 Bulunamadı: {data}")
        return -1
    
    def display(self):
        """Listeyi göster"""
        if not self.head:
            print("Liste: [BOŞ]")
            return
        
        elements = []
        current = self.head
        while current:
            elements.append(str(current.data))
            current = current.next
        
        print(f"Liste: HEAD → {' → '.join(elements)} → NULL")
        print(f"Boyut: {self.size}")

# Test
print("🔗 Singly Linked List Demo\n")
liste = SinglyLinkedList()

liste.append(10)
liste.append(20)
liste.append(30)
liste.display()

print()
liste.prepend(5)
liste.display()

print()
liste.delete(20)
liste.display()

print()
liste.search(30)
liste.search(100)
Çıktı bekleniyor...

⚡ Tail Pointer ile Optimizasyon

Sona ekleme işlemini O(1) yapmak için tail pointer kullanabiliriz:

Python Kodu
class Node:
    """Tek yönlü bağlı liste düğümü"""
    def __init__(self, data):
        self.data = data
        self.next = None

class OptimizedLinkedList:
    """Tail pointer ile optimize edilmiş liste"""
    
    def __init__(self):
        self.head = None
        self.tail = None  # Son node'u takip et!
        self.size = 0
    
    def append(self, data):
        """Sona ekle - Artık O(1)!"""
        new_node = Node(data)
        
        if not self.head:
            self.head = new_node
            self.tail = new_node
        else:
            self.tail.next = new_node  # Direkt tail'e bağla
            self.tail = new_node       # Tail'i güncelle
        
        self.size += 1
    
    def prepend(self, data):
        """Başa ekle - O(1)"""
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node
        
        if not self.tail:  # İlk elemansa tail'i de ayarla
            self.tail = new_node
        
        self.size += 1
    
    def display(self):
        elements = []
        current = self.head
        while current:
            elements.append(str(current.data))
            current = current.next
        print(f"HEAD → {' → '.join(elements)} → NULL")
        print(f"(head={self.head.data if self.head else 'None'}, tail={self.tail.data if self.tail else 'None'})")

# Test
print("⚡ Optimized Linked List (Tail Pointer)\n")
opt_list = OptimizedLinkedList()

import time
N = 10000

# Normal append (simüle)
start = time.time()
for i in range(N):
    opt_list.append(i)
print(f"✅ {N} eleman O(1) append: {time.time()-start:.4f} sn")
print(f"Son eleman: {opt_list.tail.data}")
Çıktı bekleniyor...

📊 Zaman Karmaşıklığı Özeti

Operasyon Temel Tail ile
Başa Ekleme O(1) O(1)
Sona Ekleme O(n) O(1)
Baştan Silme O(1) O(1)
Sondan Silme O(n) O(n)
Arama O(n) O(n)

⚠️ Sondan silme her zaman O(n) çünkü önceki node'u bulmak gerekir (tek yönlü!)