🌳 5.0: Ağaç (Tree) Veri Yapısı

Hiyerarşik Verilerin Temeli - BST, Heap ve Daha Fazlası

📌 Ağaç Nedir?

Ağaç, düğümlerden (node) oluşan hiyerarşik (ağaç şeklinde, yukarıdan aşağıya sıralı) bir veri yapısıdır. Her düğüm veri tutar ve bir veya daha fazla alt düğüme (child) işaret eder.

🌍 Gerçek Hayattan Örnekler

  • Soy Ağacı: Büyükbaba → Baba/Anne → Çocuklar
  • Dosya Sistemi: Kök dizin (/) → Klasörler → Dosyalar
  • Şirket Hiyerarşisi: CEO → Müdürler → Çalışanlar
  • HTML DOM: html → head, body → div, p...

🎯 Temel Kavramlar ve Terminoloji

Genel Ağaç Örneği (Çok Çocuklu)

Düğüm Tipleri

Kök (Root)
İç Düğüm
Yaprak (Leaf)

💡 Düğümlerin üzerine gelin

Bir düğümün üzerine gelin...
Kök (Root) Yapraklar (Leaves)
Bu ağaçta:
  • Kök (Root): A
  • İç Düğümler (Internal Nodes): B, C, D
  • Yapraklar (Leaf Nodes): E, F, G, H, I, J
  • A'nın çocukları: B, C, D (3 çocuk)
  • B'nin çocukları: E, F (2 çocuk)
  • D'nin çocukları: H, I, J (3 çocuk)

🌱 Kök (Root)

Ağacın en üstündeki düğüm. Parent'ı yoktur, her ağacın tam olarak 1 kökü vardır.

🍃 Yaprak (Leaf)

Çocuğu olmayan düğümler. Ağacın "uç noktaları".

👆 Parent (Ebeveyn)

Bir düğümün hemen üstündeki düğüm. Her düğümün en fazla 1 parent'ı vardır (kök hariç).

👇 Child (Çocuk)

Bir düğümün hemen altındaki düğümler. Bir düğümün 0 veya daha fazla çocuğu olabilir.

👫 Sibling (Kardeş)

Aynı parent'a sahip düğümler. B, C, D kardeştir (hepsi A'nın çocuğu).

🔗 Edge (Kenar)

İki düğüm arasındaki bağlantı. n düğümlü ağaçta n-1 kenar vardır.

📏 Derinlik ve Yükseklik

📍 Derinlik (Depth)

Kökten o düğüme olan kenar sayısı

💡 Düğümlerin üzerine gelin

📐 Yükseklik (Height)

O düğümden en uzak yaprağa olan kenar sayısı

💡 Düğümlerin üzerine gelin
💡 Önemli Notlar:

🌲 İkili Ağaç (Binary Tree)

İkili Ağaç, her düğümün en fazla 2 çocuğu olan özel bir ağaç türüdür. Çocuklar sol (left) ve sağ (right) olarak adlandırılır.

🔍 Neden İkili Ağaç Önemli?

İkili ağaçlar en yaygın ağaç türüdür çünkü basit ve verimlidir. BST (Binary Search Tree) ve Heap ikili ağaçlardır. Arama, sıralama ve önceliklendirme işlemlerinde kullanılır.

📊 İkili Ağaç Türleri

🌿 Tam İkili Ağaç (Full Binary Tree)

Her düğüm 0 ya da 2 çocuğa sahip

1 2 3 4 5

✅ Her düğüm 0 veya 2 çocuk
❌ 1 çocuklu düğüm yok

🌲 Tam Dolu İkili Ağaç (Perfect Binary Tree)

Tüm seviyeler dolu, yapraklar aynı seviyede

1 2 3 4 5 6 7

Düğüm sayısı: 2^(h+1) - 1
Yükseklik 2 için: 2³-1 = 7

🏔️ Tam İkili Ağaç (Complete Binary Tree)

⭐ Heap bu türdendir! Tüm seviyeler dolu, son seviye soldan sağa dolduruluyor

1 2 3 4 5 6 7 8 9 ← EN SOLDA!
🔑 Complete Binary Tree Kuralı:

Son seviye hariç tüm seviyeler tamamen dolu. Son seviye en soldan başlayarak dolduruluyor. Sağda boşluk olabilir, ama solda boşluk olamaz!

📦 Dizi ile Temsil (Array Representation)

Complete binary tree'de boşluk olmadığı için dizi ile verimli temsil edilir:

1[0]
2[1]
3[2]
4[3]
5[4]
6[5]
7[6]
8[7]
9[8]
🔢 İndeks Formülleri (0-tabanlı):
  • Parent(i) = (i - 1) // 2
  • Sol Çocuk(i) = 2 * i + 1
  • Sağ Çocuk(i) = 2 * i + 2

⚖️ Dengeli İkili Ağaç (Balanced)

Her düğümün sol/sağ yükseklik farkı ≤ 1

50 25 75 10 30 60 90

✅ Arama: O(log n)

❌ Dengesiz Ağaç (Degenerate)

Linked List'e dönüşmüş

10 25 30 50 ← Linked List gibi!

❌ Arama: O(n)

💻 Python'da Ağaç Kodlama

1. Çok Çocuklu Genel Ağaç

Genel Ağaç (N-ary Tree)
class TreeNode:
    """Çok çocuklu genel ağaç düğümü"""
    def __init__(self, data):
        self.data = data
        self.children = []  # Çocuklar listesi (0 veya daha fazla)

    def add_child(self, child_node):
        """Çocuk ekle"""
        self.children.append(child_node)

    def __repr__(self):
        return f"TreeNode({self.data})"

# Dosya sistemi örneği
root = TreeNode("/")
home = TreeNode("home")
usr = TreeNode("usr")
root.add_child(home)
root.add_child(usr)

user1 = TreeNode("kullanici1")
user2 = TreeNode("kullanici2")
home.add_child(user1)
home.add_child(user2)

bin_dir = TreeNode("bin")
lib_dir = TreeNode("lib")
usr.add_child(bin_dir)
usr.add_child(lib_dir)

print(f"Kök: {root.data}")
print(f"Kökün çocukları: {[c.data for c in root.children]}")
print(f"home'un çocukları: {[c.data for c in home.children]}")
print(f"usr'nin çocukları: {[c.data for c in usr.children]}")

# Ağacı yazdır (DFS - Depth First Search)
def print_tree(node, level=0):
    """Ağacı hiyerarşik yazdır"""
    indent = "  " * level
    print(f"{indent}{node.data}")
    for child in node.children:
        print_tree(child, level + 1)

print("\n=== Ağaç Yapısı ===")
print_tree(root)
Çıktı bekleniyor...

2. İkili Ağaç

Binary Tree
class BinaryTreeNode:
    """İkili ağaç düğümü - max 2 çocuk"""
    def __init__(self, data):
        self.data = data
        self.left = None   # Sol çocuk
        self.right = None  # Sağ çocuk

    def __repr__(self):
        return f"Node({self.data})"

# İkili ağaç oluştur
#        1
#       / \
#      2   3
#     / \
#    4   5

root = BinaryTreeNode(1)
root.left = BinaryTreeNode(2)
root.right = BinaryTreeNode(3)
root.left.left = BinaryTreeNode(4)
root.left.right = BinaryTreeNode(5)

print(f"Kök: {root.data}")
print(f"Sol çocuk: {root.left.data}")
print(f"Sağ çocuk: {root.right.data}")
print(f"Sol-Sol: {root.left.left.data}")
print(f"Sol-Sağ: {root.left.right.data}")

# Derinlik hesapla
def depth(node, target_node, current_depth=0):
    """Bir düğümün derinliğini hesapla"""
    if node is None:
        return -1
    if node == target_node:
        return current_depth

    left_depth = depth(node.left, target_node, current_depth + 1)
    if left_depth != -1:
        return left_depth

    return depth(node.right, target_node, current_depth + 1)

# Yükseklik hesapla
def height(node):
    """Bir düğümün yüksekliğini hesapla"""
    if node is None:
        return -1  # Boş düğüm
    if node.left is None and node.right is None:
        return 0  # Yaprak düğüm

    left_height = height(node.left)
    right_height = height(node.right)
    return max(left_height, right_height) + 1

print(f"\n=== Derinlik ve Yükseklik ===")
print(f"Kökün derinliği: {depth(root, root)}")
print(f"Kökün yüksekliği (ağacın yüksekliği): {height(root)}")
print(f"Node 2'nin derinliği: {depth(root, root.left)}")
print(f"Node 2'nin yüksekliği: {height(root.left)}")
print(f"Node 4'ün derinliği: {depth(root, root.left.left)}")
print(f"Node 4'ün yüksekliği: {height(root.left.left)}")
Çıktı bekleniyor...

3. Complete Binary Tree - Dizi ile Temsil (Heap gibi)

Complete Binary Tree - Array Implementation
class CompleteBinaryTree:
    """
    Complete binary tree - dizi ile temsil
    Heap bu yapıyı kullanır!
    """
    def __init__(self, data_list):
        self.tree = data_list

    def parent_index(self, i):
        """i indeksindeki düğümün parent indeksi"""
        if i == 0:
            return None  # Kökün parent'ı yok
        return (i - 1) // 2

    def left_child_index(self, i):
        """i indeksindeki düğümün sol çocuk indeksi"""
        left = 2 * i + 1
        return left if left < len(self.tree) else None

    def right_child_index(self, i):
        """i indeksindeki düğümün sağ çocuk indeksi"""
        right = 2 * i + 2
        return right if right < len(self.tree) else None

    def get_value(self, i):
        """İndeksteki değeri al"""
        return self.tree[i] if 0 <= i < len(self.tree) else None

    def print_tree_info(self):
        """Ağaç bilgilerini yazdır"""
        print(f"Dizi: {self.tree}")
        print(f"Düğüm sayısı: {len(self.tree)}\n")

        for i in range(len(self.tree)):
            print(f"İndeks {i} (değer: {self.tree[i]}):")

            parent_idx = self.parent_index(i)
            if parent_idx is not None:
                print(f"  Parent: indeks {parent_idx} = {self.tree[parent_idx]}")
            else:
                print(f"  Parent: yok (kök düğüm)")

            left_idx = self.left_child_index(i)
            if left_idx is not None:
                print(f"  Sol çocuk: indeks {left_idx} = {self.tree[left_idx]}")
            else:
                print(f"  Sol çocuk: yok")

            right_idx = self.right_child_index(i)
            if right_idx is not None:
                print(f"  Sağ çocuk: indeks {right_idx} = {self.tree[right_idx]}")
            else:
                print(f"  Sağ çocuk: yok")
            print()

# Complete binary tree örneği
#        1
#       / \
#      2   3
#     / \ / \
#    4  5 6  7
#   / \
#  8   9

tree_data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
tree = CompleteBinaryTree(tree_data)
tree.print_tree_info()
Çıktı bekleniyor...

🔗 Veri Yapıları Hiyerarşisi: Graf → Ağaç → Özel Ağaçlar

Genel yapıdan özele doğru iniyoruz. Her adımda yeni bir kısıtlama ekleniyor.

📊

1️⃣ Graf (Graph) - En Genel Yapı

Düğümler (vertices) ve kenarlar (edges). Herhangi bir düğüm herhangi bir düğüme bağlanabilir.

✅ Döngü olabilir ✅ Yönlü olabilir ✅ Ağırlıklı olabilir

Örnek: Facebook arkadaşlık ağı, Google Maps yol haritası

❌ Döngü yok + ✅ Bağlı
🌳

2️⃣ Ağaç (Tree) - Hiyerarşik Yapı

Döngüsüz, bağlı graf. Bir kök düğümden başlar, her düğümün tam 1 parent'ı vardır (kök hariç).

❌ Döngü yok ✅ n düğüm → n-1 kenar ✅ Çocuk sayısı sınırsız

Örnek: Dosya sistemi (bir klasör birçok alt klasör/dosya içerir), HTML DOM

Her düğümde ❌ Max 2 çocuk
🌲

3️⃣ İkili Ağaç (Binary Tree)

Her düğümün en fazla 2 çocuğu: sol çocuk (left) ve sağ çocuk (right).

✅ 0, 1, veya 2 çocuk ✅ Sol/sağ pozisyonu önemli

Neden önemli? Basit ve verimli! BST ve Heap bu yapıdadır.

+ Sıralama kuralı
🔍

4A️⃣ Binary Search Tree (BST)

İkili Ağaç + Sıralama:

Sol alt ağaç < Kök < Sağ alt ağaç

Kullanım: Sıralı veri arama/ekleme/silme
Karmaşıklık: O(log n) - dengeli ise

+ Complete + Heap kuralı
🏔️

4B️⃣ Heap (Yığın)

Complete Binary Tree + Heap özelliği:

Parent ≥ Child (Max Heap)
Parent ≤ Child (Min Heap)

Kullanım: Priority Queue
Karmaşıklık: Min/Max O(1), Ekle/Sil O(log n)

Özel Durum: Her düğümde sadece 1 çocuk (veya hiç)
🔗

5️⃣ Linked List - Dejenere Ağaç

Her düğümün sadece 1 çocuğu var (veya hiç). Ağaç doğrusal hale gelir.

❌ Ağaç avantajı kaybolur ❌ Arama: O(n) ✅ Dinamik yapı

Sonuç: Dengesiz BST Linked List'e dönüşür → O(n) karmaşıklık!

🎯 Önemli Çıkarımlar

🆚 Ağaç Türleri Karşılaştırması

Özellik Genel Ağaç İkili Ağaç BST Heap Linked List
Çocuk Sayısı 0 veya daha fazla 0, 1, veya 2 0, 1, veya 2 0, 1, veya 2 0 veya 1
Özel Kural - - Sol < Kök < Sağ Parent ≥/≤ Child -
Yapı Herhangi Herhangi Herhangi Complete Doğrusal
Arama O(n) O(n) O(log n)* O(n) O(n)
Min/Max O(n) O(n) O(log n)* O(1) O(n)
Depolama Pointer Pointer Pointer Dizi Pointer
Kullanım Dosya sistemi Genel Sıralı veri Priority Queue Dinamik liste

* Dengeli ise. Dengesiz BST O(n) olabilir.

📋 Özet ve Sonraki Adımlar

✅ Bu Bölümde Öğrendikleriniz

📚 Sonraki Konular