🚶 5.3: BST Dolaşma (Traversal)

Ağacın Tüm Elemanlarını Ziyaret Etme Yöntemleri

📌 Bu Bölümde Öğrenecekleriniz

Ağaç üzerinde dolaşma (traversal), tüm düğümleri belirli bir sırayla ziyaret etme işlemidir. Dört temel yöntem vardır:

📋

Inorder

Sol → Kök → Sağ

📦

Preorder

Kök → Sol → Sağ

🗑️

Postorder

Sol → Sağ → Kök

📊

Level Order

Seviye seviye (BFS)

🤔 Traversal Nedir ve Neden Önemli?

Bir ağaçtaki tüm düğümleri belirli bir sırayla ziyaret etmeye traversal (dolaşma) denir. Peki neden birden fazla yöntem var?

💡 Farklı Problemler, Farklı Traversal

Her traversal yöntemi farklı bir problemi çözmek için optimize edilmiştir:

  • Sıralı liste istiyorsanız: Inorder → BST'de küçükten büyüğe sıralı çıktı
  • Ağacı kopyalamak/serialize etmek istiyorsanız: Preorder → Kök önce, yapı korunur
  • Ağacı silmek istiyorsanız: Postorder → Önce çocukları sil, sonra kökü
  • Seviye seviye işlem yapacaksanız: Level Order (BFS) → En kısa yol, derinlik

🎯 Örnek Ağaç

Tüm traversal örneklerinde bu ağacı kullanacağız:

🔑 Traversal'ı Anlamanın Anahtarı: Recursive Düşünce

Her traversal yöntemi aynı kalıbı izler. Recursive fonksiyon şu yapıya sahiptir:

Inorder
left()
print
right()
Preorder
print
left()
right()
Postorder
left()
right()
print

Tek fark: "print" (kökü işle) satırının nerede olduğu!

⚡ Hızlı Karşılaştırma: Aynı Ağaç, Farklı Çıktılar

📋 Inorder:
20 → 30 → 40 → 50 → 60 → 70 → 80
Sıralı! (BST'nin büyüsü)
📦 Preorder:
50 → 30 → 20 → 40 → 70 → 60 → 80
Kök başta
🗑️ Postorder:
20 → 40 → 30 → 60 → 80 → 70 → 50
Kök sonda
📊 Level Order:
50 | 30, 70 | 20, 40, 60, 80
Seviye seviye

🔍 Detaylı Recursive Call Trace: Inorder Örneği

Recursion'u anlamak için inorder traversal'ı adım adım takip edelim:

📞 Call Stack Simülasyonu

inorder(50)
inorder(30)
inorder(20)
inorder(None) → return
print(20)
inorder(None) → return
print(30)
inorder(40) → ... → print(40)
print(50)
inorder(70) → ...

📝 Çıktı Oluşumu

20 30 40 50 ...
💡 Neden Sıralı?

BST'de sol alt ağaçtaki tüm değerler kökten küçük, sağ alt ağaçtaki tüm değerler kökten büyük. Inorder ile önce sol (küçükler), sonra kök, sonra sağ (büyükler) ziyaret edilir → Sonuç sıralı!

📋

Inorder (Sol-Kök-Sağ)

LNR: Left → Node → Right

Algoritma:
1. Sol alt ağacı ziyaret et (recursive)
2. Kökü ziyaret et (işle) ← print burada!
3. Sağ alt ağacı ziyaret et (recursive)
🧠 Düşünce Şekli

"Önce solumu bitireyim, sonra kendimi söyleyeyim, en son sağımı halledeyim."

Sonuç:

20 30 40 50 60 70 80

✨ BST için inorder = SIRALI çıktı! (Küçükten büyüğe)

📊
Sıralı Liste

BST → Array

BST Doğrulama

Sıralı mı kontrol

🔢
k'ıncı En Küçük

Sırada k. eleman

📦

Preorder (Kök-Sol-Sağ)

NLR: Node → Left → Right

Algoritma:
1. Kökü ziyaret et (işle) ← print burada!
2. Sol alt ağacı ziyaret et (recursive)
3. Sağ alt ağacı ziyaret et (recursive)
🧠 Düşünce Şekli

"Önce kendimi söyleyeyim, sonra sol tarafa gideyim, en son sağ tarafa bakayım."

Sonuç:

50 30 20 40 70 60 80

✨ Kök her zaman İLK! Ağacı kopyalamak/serialize etmek için ideal.

💾
Ağaç Kopyalama

Clone işlemleri

📁
Dosya Sistemi

Klasör yapısı

🔗
Serileştirme

JSON, XML export

🗑️

Postorder (Sol-Sağ-Kök)

LRN: Left → Right → Node

Algoritma:
1. Sol alt ağacı ziyaret et (recursive)
2. Sağ alt ağacı ziyaret et (recursive)
3. Kökü ziyaret et (işle) ← print burada!
🧠 Düşünce Şekli

"Önce solumu hallettim, sonra sağımı hallettim, artık kendimi söyleyebilirim."

Sonuç:

20 40 30 60 80 70 50

✨ Kök her zaman SON! Ağacı silmek için ideal (önce çocukları sil).

🗑️
Ağaç Silme

Çocuklar önce silinir

🧮
İfade Değerlendirme

Postfix hesaplama

📐
Boyut Hesaplama

Alt ağaç boyutları

📊

Level Order (BFS)

Seviye seviye, soldan sağa

Algoritma (Queue kullanarak):
1. Kökü queue'ya ekle
2. Queue boş olana kadar:
   - Queue'dan çıkar ve işle
   - Çocuklarını queue'ya ekle
🧠 Düşünce Şekli

"Yukarıdan aşağıya, soldan sağa, seviye seviye gidiyorum."

⚠️ Önemli Fark

Bu yöntem recursive değil, iteratif! Queue (FIFO) veri yapısı kullanır.

Sonuç:

50 | 30 70 | 20 40 60 80

✨ Her seviye sırayla! En kısa yol bulma, seviye sayma için ideal.

🗺️
En Kısa Yol

BFS garantisi

📏
Derinlik Bulma

Seviye sayısı

🖨️
Seviye Yazdırma

Katman katman

🎮 İnteraktif Traversal Simülasyonu

Bir traversal yöntemi seçin ve adım adım izleyin. Her adımda hangi düğümün ziyaret edildiğini ve call stack'in durumunu görün.

📚 Call Stack / Queue
✅ Çıktı (Output)
💬 Açıklama
Adım: 0 / 0 Inorder

💻 Python Implementasyonu

Tüm Traversal Metodları
from collections import deque

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

class BST:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if not self.root:
            self.root = Node(data)
            return
        c = self.root
        while True:
            if data < c.data:
                if not c.left: c.left = Node(data); return
                c = c.left
            else:
                if not c.right: c.right = Node(data); return
                c = c.right

    # ===== TRAVERSAL METODLARI =====

    def inorder(self, node=None, result=None):
        """Sol → Kök → Sağ (Sıralı çıktı)"""
        if result is None: result = []
        if node is None: node = self.root

        if node:
            self.inorder(node.left, result)   # Sol
            result.append(node.data)           # Kök
            self.inorder(node.right, result)  # Sağ
        return result

    def preorder(self, node=None, result=None):
        """Kök → Sol → Sağ"""
        if result is None: result = []
        if node is None: node = self.root

        if node:
            result.append(node.data)           # Kök
            self.preorder(node.left, result)  # Sol
            self.preorder(node.right, result) # Sağ
        return result

    def postorder(self, node=None, result=None):
        """Sol → Sağ → Kök"""
        if result is None: result = []
        if node is None: node = self.root

        if node:
            self.postorder(node.left, result)  # Sol
            self.postorder(node.right, result) # Sağ
            result.append(node.data)            # Kök
        return result

    def level_order(self):
        """BFS ile seviye seviye dolaşma"""
        if not self.root:
            return []

        result = []
        queue = deque([self.root])

        while queue:
            node = queue.popleft()  # FIFO
            result.append(node.data)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        return result

# Test
bst = BST()
for v in [50, 30, 70, 20, 40, 60, 80]:
    bst.insert(v)

print("🌳 Ağaç: 50 → (30, 70) → (20, 40, 60, 80)")
print()
print("=" * 50)
print("📋 TRAVERSAL SONUÇLARI")
print("=" * 50)
print()
print(f"📋 Inorder  (LNR): {bst.inorder()}")
print(f"   → Sıralı çıktı verir!")
print()
print(f"📦 Preorder (NLR): {bst.preorder()}")
print(f"   → Kök başta, kopyalama için")
print()
print(f"🗑️ Postorder(LRN): {bst.postorder()}")
print(f"   → Kök sonda, silme için")
print()
print(f"📊 Level Order:    {bst.level_order()}")
print(f"   → Seviye seviye, BFS")
Çıktı bekleniyor...

📊 Özet ve Karşılaştırma

🎯 Hangi Traversal Ne Zaman?

📋 Inorder
  • BST → Sıralı liste
  • BST doğrulama
  • k'ıncı küçük eleman
📦 Preorder
  • Ağaç kopyalama
  • Serialize (JSON)
  • Prefix ifade
🗑️ Postorder
  • Ağaç silme
  • Postfix hesaplama
  • Alt ağaç boyutu
📊 Level Order
  • En kısa yol (BFS)
  • Derinlik bulma
  • Seviye yazdırma
Traversal Sıra Karmaşıklık Veri Yapısı Özellik
📋 Inorder L → N → R O(n) Stack (recursive) BST'de sıralı çıktı verir
📦 Preorder N → L → R O(n) Stack (recursive) Kök her zaman ilk
🗑️ Postorder L → R → N O(n) Stack (recursive) Kök her zaman son
📊 Level Order Seviye ↓ O(n) Queue (iteratif) Breadth-First Search

💡 Önemli Noktalar

  • Tüm traversal'lar O(n) zamanda çalışır çünkü her düğüm tam olarak bir kez ziyaret edilir.
  • BST özelliği: Sadece Inorder traversal BST'nin sıralama özelliğini kullanır ve sıralı çıktı verir.

🔄 Recursion (Özyineleme) Nasıl Çalışır?

Traversal kodlarını anlamadan önce, recursion'ın nasıl çalıştığını anlamak çok önemli. Hadi adım adım bakalım!

🤔 Recursion Nedir?

Bir fonksiyonun kendini çağırmasına recursion denir. Ama nasıl çalışıyor?

Düşün ki bir aynaya bakıyorsun ve arkanda da bir ayna var. Sonsuz yansıma görürsün, değil mi? Recursion da böyle - ama bir durma noktası (base case) olmalı!

📚 Call Stack (Çağrı Yığını) Nedir?

Bilgisayar, fonksiyon çağrılarını takip etmek için call stack (çağrı yığını) adlı bir yapı kullanır. Bu, tam olarak bir tabak yığını gibi çalışır:

🍽️ Gerçek Hayat Analojisi

Tabak 3 (en üstte)
Tabak 2
Tabak 1 (en altta)
  • Yeni tabak koyarsın → üste koyarsın
  • Tabak alırsın → üstten alırsın
  • Bu sisteme LIFO denir: Last In, First Out

💻 Programda Call Stack

inorder(20) ← şu an burada
inorder(30) bekliyor...
inorder(50) bekliyor...
  • Yeni fonksiyon çağrılır → stack'e eklenir
  • Fonksiyon biter → stack'ten çıkarılır
  • En üstteki fonksiyon şu an çalışan

👁️ Adım Adım Örnek: Basit Recursive Fonksiyon

Önce çok basit bir örnekle başlayalım:

# 3'ten geriye say
def
countdown(n):
  if n == 0:    # Base case!
    print("Bitti!")
    return
  print(n)
  countdown(n - 1)  # Kendini çağır

countdown(3)
📊 Ne Oluyor?
1️⃣ countdown(3) çağrıldı → "3" yazdır
   2️⃣ countdown(2) çağrıldı → "2" yazdır
      3️⃣ countdown(1) çağrıldı → "1" yazdır
         4️⃣ countdown(0) → "Bitti!" ✓
         ⬅️ countdown(0) bitti, geri dön
      ⬅️ countdown(1) bitti, geri dön
   ⬅️ countdown(2) bitti, geri dön
⬅️ countdown(3) bitti, program sona erdi

🔑 Kritik Kavramlar

🛑 Base Case

Recursion'ın durma noktası. Olmazsa sonsuz döngü → Stack Overflow!

🔁 Recursive Case

Fonksiyonun kendini çağırdığı kısım. Her çağrıda problem küçülmeli!

📚 Call Stack

Bekleyen fonksiyonların tutulduğu yer. Her çağrı stack'e eklenir.

🌳 Traversal'da Recursion: Adım Adım İzleme

Şimdi basit bir ağaçla inorder traversal'ı adım adım takip edelim:

Örnek Ağaç

Sadece 3 düğüm: 2, 1, 3

Inorder: Sol → Kök → Sağ
Adım 1: inorder(2) çağrıldı
→ "Önce soluma git" → inorder(1) çağır
Stack: [inorder(2)]
Adım 2: inorder(1) çağrıldı
→ Sol yok (None) → kendimi yazdır: 1 ✓
→ Sağ yok (None) → geri dön
Stack: [inorder(2)] ← inorder(1) bitti
Adım 3: inorder(2)'ye geri döndük
→ Sol bitti, şimdi kendimi yazdır: 2 ✓
→ Şimdi sağa git → inorder(3) çağır
Adım 4: inorder(3) çağrıldı
→ Sol yok → kendimi yazdır: 3 ✓
→ Sağ yok → geri dön → BİTTİ!
Sonuç: 1 → 2 → 3 (Sıralı!)

📦 Space Complexity (Bellek Karmaşıklığı): O(h)

Bir algoritmanın space complexity'si, çalışırken ne kadar ek bellek kullandığını gösterir.

🤔 O(h) Ne Demek?

Traversal'da space complexity O(h)'dir. Burada:

  • h = ağacın yüksekliği (height)
  • n = ağaçtaki toplam düğüm sayısı

Neden O(n) değil? Çünkü call stack'te aynı anda en fazla h tane fonksiyon bekler!

🎨 Görsel Açıklama: Stack Derinliği

✅ Dengeli Ağaç (Balanced)

7 düğüm var ama stack derinliği sadece 3!

n = 7 düğüm
h = 3 seviye (yükseklik)
Stack: max 3 fonksiyon bekler
Genel formül: h ≈ log₂(n)
n = 1.000.000 → h ≈ 20

⚠️ Dengesiz Ağaç (Tek Yönlü)

7 düğüm ve stack derinliği de 7!

n = 7 düğüm
h = 7 seviye (yükseklik)
Stack: 7 fonksiyon bekler 😰
En kötü durum: h = n
n = 10.000 → h = 10.000 → Stack Overflow!

💡 Neden Bu Kadar Önemli?

Her programın kullanabileceği stack belleği sınırlıdır:

  • Python: Varsayılan ~1000 çağrı limiti
  • JavaScript: Tarayıcıya göre değişir (~10.000-50.000)
  • C/C++: Varsayılan ~1-8 MB stack

Eğer ağaç çok derin olursa ve bu limiti aşarsak → Stack Overflow Error!

⚠️ Recursive Çağrıların Gizli Maliyeti

Recursive kod yazmak kolay ve okunabilir. Ama arka planda neler oluyor?

📦 Her Fonksiyon Çağrısında Ne Olur?

Bir fonksiyon çağrıldığında, bilgisayar şu bilgileri stack'e kaydeder:

📍
Return Adresi

Fonksiyon bitince nereye dönecek?

🔗
Parametreler

Fonksiyona gönderilen değerler (node, vb.)

📦
Lokal Değişkenler

Fonksiyon içindeki tüm değişkenler

🏠
Stack Frame Bilgisi

Önceki fonksiyonun durumu

🧮 Somut Örnek: 1000 Düğümlü Dengesiz Ağaç

Her fonksiyon çağrısı yaklaşık 50-100 byte bellek kullanır. 1000 düğümlü dengesiz ağaçta:

1000
recursive çağrı
×100 byte
her çağrı için
≈ 100 KB
toplam bellek

Bu çok fazla değil gibi görünebilir, ama Python'un varsayılan limiti 1000 çağrı! Ve her çağrıda ek işlem süresi de harcanır.

📊 Recursive vs Iterative Karşılaştırması

Özellik Recursive Iterative
Kod Okunabilirliği
Kodu anlama kolaylığı
✅ Çok Kolay
Doğal ve sezgisel
⚠️ Orta
Stack yönetimi gerekir
Stack Overflow Riski
Derin ağaçlarda
❌ VAR
Sistem stack'i sınırlı
✅ YOK
Kendi stack'imizi yönetiriz
Fonksiyon Çağrı Maliyeti
Her düğümde ek işlem
⚠️ Yüksek
Her çağrıda overhead
✅ Düşük
Sadece döngü
Bellek Kontrolü
Ne kadar bellek kullanıldığını bilmek
❌ Yok
Sistem yönetir
✅ Tam Kontrol
Stack boyutunu biz belirleriz
Erken Durma
İstediğimiz yerde sonlandırma
⚠️ Zor
Flag değişkeni gerekir
✅ Kolay
break ile çıkabiliriz

🐍 Python'da Stack Limiti

Python varsayılan olarak ~1000 recursive çağrıya izin verir:

# Limiti görmek için:
import sys
print(sys.getrecursionlimit())  # Çıktı: 1000

# Limiti artırmak için (DİKKATLİ!):
sys.setrecursionlimit(10000)  # Tehlikeli olabilir!

⚠️ Uyarı: Limiti artırmak Stack Overflow yerine programın çökmesine neden olabilir. Derin ağaçlar için iterative çözüm daha güvenli!

🔄 Iterative Traversal: Stack ile Recursive'siz Çözüm

Recursive traversal'lar sistem stack'ini kullanır. Peki ya kendi stack'imizi oluştursak? İşte iterative traversal'ın mantığı bu!

💡 Temel Fikir

Recursive çağrı yerine:

  1. Python listesi ile kendi stack'imizi oluşturuyoruz
  2. append() ile stack'e ekliyoruz (push)
  3. pop() ile stack'ten çıkarıyoruz
  4. Stack boşalana kadar while döngüsü ile devam ediyoruz

🔧 Neden Iterative Tercih Edilir?

🛡️
Stack Overflow Yok

Derin ağaçlarda bile güvenli

Daha Hızlı

Fonksiyon çağrı maliyeti yok

🎛️
Tam Kontrol

İstediğimiz yerde durabiliriz

📊
Bellek Yönetimi

Stack boyutunu biz kontrol ederiz

Iterative Traversal Implementasyonları
class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

class BST:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if not self.root:
            self.root = Node(data)
            return
        c = self.root
        while True:
            if data < c.data:
                if not c.left: c.left = Node(data); return
                c = c.left
            else:
                if not c.right: c.right = Node(data); return
                c = c.right

    # ========== ITERATIVE TRAVERSAL ==========
    
    def inorder_iterative(self):
        """
        Iterative Inorder: Stack kullanarak Sol → Kök → Sağ
        
        Mantık: 
        1. Mümkün olduğunca sola git, yolda herkesi stack'e at
        2. Stack'ten çıkar, değerini al
        3. Sağ çocuğa geç, tekrarla
        """
        result = []
        stack = []
        current = self.root
        
        while stack or current:
            # Sol tarafa git, yolda herkesi stack'e at
            while current:
                stack.append(current)
                current = current.left
            
            # Stack'ten çıkar (en soldaki)
            current = stack.pop()
            result.append(current.data)  # ZİYARET
            
            # Sağ tarafa geç
            current = current.right
        
        return result
    
    def preorder_iterative(self):
        """
        Iterative Preorder: Stack kullanarak Kök → Sol → Sağ
        
        Mantık:
        1. Kökü stack'e at
        2. Stack'ten çıkar, ziyaret et
        3. ÖNCE sağı, SONRA solu stack'e at (LIFO olduğu için sol önce çıkar)
        """
        if not self.root:
            return []
        
        result = []
        stack = [self.root]
        
        while stack:
            node = stack.pop()
            result.append(node.data)  # ZİYARET
            
            # Sağı önce at (sonra çıksın)
            if node.right:
                stack.append(node.right)
            # Solu sonra at (önce çıksın)
            if node.left:
                stack.append(node.left)
        
        return result
    
    def postorder_iterative(self):
        """
        Iterative Postorder: İki stack veya ters preorder
        
        Mantık (Ters Preorder yöntemi):
        1. Preorder: Kök → Sol → Sağ
        2. Değiştirilmiş: Kök → Sağ → Sol
        3. Sonucu ters çevir: Sol → Sağ → Kök = Postorder!
        """
        if not self.root:
            return []
        
        result = []
        stack = [self.root]
        
        while stack:
            node = stack.pop()
            result.append(node.data)
            
            # Solu önce at (sonra çıksın)
            if node.left:
                stack.append(node.left)
            # Sağı sonra at (önce çıksın)
            if node.right:
                stack.append(node.right)
        
        # Ters çevir: Kök→Sağ→Sol --> Sol→Sağ→Kök
        return result[::-1]
    
    def postorder_iterative_2stack(self):
        """
        İki Stack ile Postorder (daha anlaşılır versiyon)
        """
        if not self.root:
            return []
        
        stack1 = [self.root]
        stack2 = []
        
        while stack1:
            node = stack1.pop()
            stack2.append(node.data)
            
            if node.left:
                stack1.append(node.left)
            if node.right:
                stack1.append(node.right)
        
        # stack2'yi ters çevir
        return stack2[::-1]

# ========== TEST ==========
bst = BST()
for v in [50, 30, 70, 20, 40, 60, 80]:
    bst.insert(v)

print("🌳 Ağaç: 50 → (30, 70) → (20, 40, 60, 80)")
print()
print("=" * 55)
print("🔄 ITERATIVE TRAVERSAL SONUÇLARI")
print("=" * 55)
print()
print(f"📋 Inorder  Iterative: {bst.inorder_iterative()}")
print(f"📦 Preorder Iterative: {bst.preorder_iterative()}")
print(f"🗑️ Postorder Iterative: {bst.postorder_iterative()}")
print(f"🗑️ Postorder 2-Stack:   {bst.postorder_iterative_2stack()}")
print()
print("💡 Tüm sonuçlar recursive versiyonlarla aynı!")
Çıktı bekleniyor...

🧠 Iterative Mantığı Anlamak

📋 Inorder Iterative

// Sola git, stack'e at
while current:
  stack.push(current)
  current = left

// Stack'ten çıkar
node = stack.pop()
visit(node)
current = node.right

Anahtar: "Sola git, çıkarken ziyaret et, sağa dön"

📦 Preorder Iterative

stack.push(root)

while stack:
  node = stack.pop()
  visit(node)
  push(right) // önce sağ
  push(left) // sonra sol

Anahtar: "Çıkarınca ziyaret et, sağı önce at (LIFO)"

🗑️ Postorder Iterative

// Preorder ama sol-sağ ters
  visit(node)
  push(left)
  push(right)

// Sonucu ters çevir
result.reverse()

Anahtar: "Ters preorder + reverse = postorder"

🎯 Ne Zaman Recursive, Ne Zaman Iterative?

✅ Recursive Tercih Et:
  • Ağaç yüksekliği sınırlı (h < 1000)
  • Kod okunabilirliği önemli
  • Prototip/demo kodu yazıyorsun
  • Tail-call optimization destekli dil
✅ Iterative Tercih Et:
  • Ağaç çok derin olabilir
  • Production/kritik kod
  • Bellek kısıtlı ortam
  • Traversal'ı yarıda kesebilmek gerekiyor