İkili ağaç oluşturma, inorder/preorder/postorder/level-order traversal adım adım görsellerle, düğüm ekleme-silme, yükseklik-düğüm-yaprak sayısı hesaplama ve C kodu.
🗂️ Data Structures📅 21 February 2026👁️ 10 views
İkili Ağaç Nedir?
İkili ağaç (binary tree), her düğümün en fazla iki çocuğa sahip olduğu özel bir ağaç türüdür. Bu iki çocuk sol (left) ve sağ (right) olarak adlandırılır. Sıra önemlidir: bir düğümün sadece sol çocuğu olması ile sadece sağ çocuğu olması farklı ağaçlardır.
İkili ağaç, ağaç ailesinin en temel ve en yaygın kullanılan formudur. BST, AVL, heap, Huffman tree — hepsi ikili ağaç üzerine inşa edilmiştir. Bu bölümde ikili ağacın temel operasyonlarını öğreneceğiz: oluşturma, dolaşma (traversal), ekleme, silme ve ölçüm.
Referans Ağaç
Bu bölüm boyunca tüm örnekleri aşağıdaki 7 düğümlü ikili ağaç üzerinde göstereceğiz:
Inorder dolaşma, ağacı sol alt ağaç → kök → sağ alt ağaç sırasıyla ziyaret eder. İkili arama ağacında (BST) inorder dolaşma, düğümleri küçükten büyüğe sıralı olarak verir — bu yüzden en önemli dolaşma yöntemidir.
Inorder algoritması (özyinelemeli): 1. Sol alt ağacı inorder dolaş 2. Kökü ziyaret et (yazdır) 3. Sağ alt ağacı inorder dolaş
Temel durum: düğüm NULL ise geri dön.
Adım Adım — Özyineleme Takibi
Referans ağacımızda inorder dolaşmanın her özyinelemeli çağrısını takip edelim. Her satırda: hangi düğüme gidildiği, NULL'a dönüş ve veri yazdırma anı gösterilir.
Inorder: Sol → Kök → Sağ
Çıkış: 4 → 2 → 5 → 1 → 6 → 3 → 7
Özyineleme Çağrı Yığını — Her Adım:
1
inorder(1) — kök. Önce sola git.
2
inorder(2) — 1'in solu. Önce sola git.
3
inorder(4) — 2'nin solu. Önce sola git.
4
inorder(NULL) — 4'ün solu yok → geri dön
5
★ Yazdır: 4 — Sol bitti, kökü ziyaret et
[ 4 ]
6
inorder(NULL) — 4'ün sağı yok → geri dön
7
★ Yazdır: 2 — Sol(4) bitti, kökü ziyaret et
[ 4, 2 ]
8
inorder(5) — 2'nin sağı. Önce sola git.
9
inorder(NULL) — 5'in solu yok → geri dön
10
★ Yazdır: 5 — Sol bitti, kökü ziyaret et
[ 4, 2, 5 ]
11
inorder(NULL) — 5'in sağı yok → geri dön
12
★ Yazdır: 1 — Sol alt ağaç(2) bitti, kökü ziyaret et
[ 4, 2, 5, 1 ]
13
inorder(3) — 1'in sağı. Önce sola git.
14
inorder(6) — 3'ün solu. Önce sola git.
15
inorder(NULL) — 6'nın solu yok → geri dön
16
★ Yazdır: 6
[ 4, 2, 5, 1, 6 ]
17
inorder(NULL) — 6'nın sağı yok → geri dön
18
★ Yazdır: 3
[ 4, 2, 5, 1, 6, 3 ]
19
inorder(7) — 3'ün sağı.
20
inorder(NULL) — 7'nin solu yok → geri dön
21
★ Yazdır: 7
[ 4, 2, 5, 1, 6, 3, 7 ]
22
inorder(NULL) — 7'nin sağı yok → geri dön
23
Tüm çağrılar tamamlandı ✓
4 2 5 1 6 3 7
NULL kontrolü neden önemli? Her yaprak düğümün iki NULL çocuğu var. Bu NULL'lar özyinelemenin "temel durumu"dur — olmadan fonksiyon sonsuza kadar çağrılır (stack overflow). 7 düğümlü ağaçta 8 NULL kontrolü yapılır (yaprakların sol/sağ'ları + tek çocuklular).
Inorder Kodu
inorder()
voidinorder(Node* node) {
if (node == NULL) return;
inorder(node->left); // 1. Sol alt ağaçprintf("%d ", node->data); // 2. Kökü yazdırinorder(node->right); // 3. Sağ alt ağaç
}
// Çıktı: 4 2 5 1 6 3 7
Neden "in-order"? Kök, sol ve sağın arasında (in the middle of) ziyaret edildiği için "inorder" adını alır. BST'de bu, elemanların sıralı (ascending) çıkmasını garanti eder çünkü sol < kök < sağ kuralı geçerlidir.
Preorder Traversal (Kök → Sol → Sağ)
Preorder dolaşma, her düğümü önce kendi, sonra sol ve sağ alt ağaçları ziyaret eder. Kök her zaman ilk yazdırılır — bu yüzden ağacın yapısını "kopyalamak" veya "serileştirmek" için idealdir. Bir ağacı dosyaya kaydetmek istiyorsanız preorder kullanırsınız.
Preorder algoritması (özyinelemeli): 1. Kökü ziyaret et (yazdır) 2. Sol alt ağacı preorder dolaş 3. Sağ alt ağacı preorder dolaş
Temel durum: düğüm NULL ise geri dön.
Ziyaret Sırası — Görsel
Preorder: Kök → Sol → Sağ
Çıkış: 1 → 2 → 4 → 5 → 3 → 6 → 7
Adım Adım — Özyineleme Takibi
Preorder: Önce yazdır, sonra sola git, sonra sağa git
1
preorder(1) — ★ Yazdır: 1 — kökü hemen yazdır
[ 1 ]
2
preorder(2) — 1'in soluna git
3
★ Yazdır: 2 — hemen yazdır, sonra sola
[ 1, 2 ]
4
preorder(4) — 2'nin soluna git
5
★ Yazdır: 4
[ 1, 2, 4 ]
6
preorder(NULL) — 4'ün solu yok → geri
7
preorder(NULL) — 4'ün sağı yok → geri
8
preorder(5) — 2'nin sağına git
9
★ Yazdır: 5
[ 1, 2, 4, 5 ]
10
preorder(NULL) — 5'in solu yok → geri
11
preorder(NULL) — 5'in sağı yok → geri
12
preorder(3) — 1'in sağına git
13
★ Yazdır: 3
[ 1, 2, 4, 5, 3 ]
14
preorder(6) — 3'ün soluna git
15
★ Yazdır: 6
[ 1, 2, 4, 5, 3, 6 ]
16
preorder(NULL) — 6'nın solu yok → geri
17
preorder(NULL) — 6'nın sağı yok → geri
18
preorder(7) — 3'ün sağına git
19
★ Yazdır: 7
[ 1, 2, 4, 5, 3, 6, 7 ]
20
preorder(NULL) — 7'nin solu yok → geri
21
preorder(NULL) — 7'nin sağı yok → geri
22
Tüm çağrılar tamamlandı ✓
1 2 4 5 3 6 7
Preorder'ın farkı: Inorder'da yazdırma sol alt ağaç bittikten sonra yapılıyordu (adım 5, 7, 10, 12...). Preorder'da yazdırma düğüme ilk ulaşıldığında yapılır (adım 1, 3, 5, 9, 13...). Bu yüzden kök her zaman çıkışta ilk elemandır.
Preorder Kodu
preorder()
voidpreorder(Node* node) {
if (node == NULL) return;
printf("%d ", node->data); // 1. Kökü yazdır ★preorder(node->left); // 2. Sol alt ağaçpreorder(node->right); // 3. Sağ alt ağaç
}
// Çıktı: 1 2 4 5 3 6 7
Preorder kullanım alanları: Ağacı dosyaya serileştirme/deserileştirme (ağacın tam yapısını korur), ağaç kopyalama (prefix expression), dizin yapısı listeleme (dosya sistemi tree komutu preorder kullanır).
Postorder Traversal (Sol → Sağ → Kök)
Postorder dolaşma, her düğümü en son ziyaret eder: önce sol alt ağaç, sonra sağ alt ağaç tamamen biter, ardından kök yazdırılır. Bu yüzden kök her zaman çıkışta son elemandır. Ağacı silmek (free etmek) için postorder idealdir — yapraklar önce silinir, kök en son.
Postorder algoritması (özyinelemeli): 1. Sol alt ağacı postorder dolaş 2. Sağ alt ağacı postorder dolaş 3. Kökü ziyaret et (yazdır)
Temel durum: düğüm NULL ise geri dön.
Ziyaret Sırası — Görsel
Postorder: Sol → Sağ → Kök
Çıkış: 4 → 5 → 2 → 6 → 7 → 3 → 1
Adım Adım — Özyineleme Takibi
Postorder: Önce sola git, sonra sağa git, en son yazdır
1
postorder(1) — kök. Yazdırmadan önce sola git.
2
postorder(2) — sola git
3
postorder(4) — sola git
4
postorder(NULL) — 4'ün solu yok → geri
5
postorder(NULL) — 4'ün sağı yok → geri
6
★ Yazdır: 4 — sol ve sağ bitti, şimdi kökü yazdır
[ 4 ]
7
postorder(5) — 2'nin sağına git
8
postorder(NULL) — 5'in solu yok → geri
9
postorder(NULL) — 5'in sağı yok → geri
10
★ Yazdır: 5
[ 4, 5 ]
11
★ Yazdır: 2 — sol(4) ve sağ(5) bitti, şimdi 2'yi yazdır
[ 4, 5, 2 ]
12
postorder(3) — 1'in sağına git
13
postorder(6) — sola git
14
postorder(NULL) → geri
15
postorder(NULL) → geri
16
★ Yazdır: 6
[ 4, 5, 2, 6 ]
17
postorder(7) — 3'ün sağına git
18
postorder(NULL) → geri
19
postorder(NULL) → geri
20
★ Yazdır: 7
[ 4, 5, 2, 6, 7 ]
21
★ Yazdır: 3 — sol(6) ve sağ(7) bitti
[ 4, 5, 2, 6, 7, 3 ]
22
★ Yazdır: 1 — sol alt ağaç(2) ve sağ alt ağaç(3) bitti → kök en son
[ 4, 5, 2, 6, 7, 3, 1 ]
23
Tüm çağrılar tamamlandı ✓
4 5 2 6 7 3 1
Postorder'ın gizli gücü: Yazdırma her zaman en son yapıldığı için, düğüm yazdırıldığında alt ağaçlarının tamamı zaten işlenmiştir. Bu özellik "aşağıdan yukarı" hesaplamaları (yükseklik, boyut, bellek toplama gibi) mümkün kılar. Ağacı silerken de postorder kullanılır: önce çocukları free et, sonra kendini — aksi halde erişilemeyen düğümler kalır (bellek sızıntısı).
Postorder Kodu
postorder()
voidpostorder(Node* node) {
if (node == NULL) return;
postorder(node->left); // 1. Sol alt ağaçpostorder(node->right); // 2. Sağ alt ağaçprintf("%d ", node->data); // 3. Kökü yazdır ★
}
// Çıktı: 4 5 2 6 7 3 1
Üç DFS Traversal — Karşılaştırma
Inorder, preorder ve postorder aynı DFS (depth-first search) mantığını kullanır — fark sadece yazdırma satırının yeridir:
Tek Fark: printf'in Yeri
Preorder
★ printf
recurse(left)
recurse(right)
Inorder
recurse(left)
★ printf
recurse(right)
Postorder
recurse(left)
recurse(right)
★ printf
Traversal
Sıra
Kök pozisyonu
Çıktı
Temel kullanım
Preorder
Kök → Sol → Sağ
İlk
1 2 4 5 3 6 7
Serileştirme, kopyalama
Inorder
Sol → Kök → Sağ
Ortada
4 2 5 1 6 3 7
BST sıralı çıktı
Postorder
Sol → Sağ → Kök
Son
4 5 2 6 7 3 1
Silme, yükseklik hesaplama
Hatırlatma formülü: "pre" = önce yazdır, "in" = arada yazdır, "post" = sonra yazdır. Yazdırmanın yeri = traversal adı. Üçünde de sol her zaman sağ'dan önce gelir.
Level-Order Traversal (Seviye Sırasıyla — BFS)
Önceki üç dolaşma (inorder, preorder, postorder) hep derinlik öncelikli (DFS — Depth-First Search) mantığıyla çalışıyordu: bir dalı sonuna kadar takip et, sonra geri dön. Level-order ise tamamen farklı bir strateji kullanır: genişlik öncelikli (BFS — Breadth-First Search). Ağacı seviye seviye, soldan sağa dolaşır — tıpkı bir kitabı satır satır okumak gibi.
Level-order algoritması: 1. Kökü bir kuyruğa (queue) ekle. 2. Kuyruk boş olana kadar tekrarla: a. Kuyruktan bir düğüm çıkar (dequeue), yazdır. b. Sol çocuğu varsa kuyruğa ekle (enqueue). c. Sağ çocuğu varsa kuyruğa ekle (enqueue).
Özyineleme kullanmaz! Kuyruk (FIFO) doğal olarak seviye sırasını korur.
DFS vs BFS — Temel Fark: DFS stack kullanır (özyineleme = implicit stack). BFS queue kullanır. Stack son gireni önce çıkarır → derinliğe iner. Queue ilk gireni önce çıkarır → aynı seviyeyi bitirir, sonra aşağıya geçer.
Ziyaret Sırası — Seviye Seviye
Level-Order: Seviye 0 → Seviye 1 → Seviye 2
Çıkış: 1 → 2 → 3 → 4 → 5 → 6 → 7
Adım Adım — Kuyruk Durumu Takibi
Her adımda kuyruktan bir düğüm çıkarılır, çocukları kuyruğa eklenir. Kuyruğun FIFO yapısı seviye sırasını doğal olarak korur:
Level-Order — 7 Adımda Kuyruk Durumu
Başlangıç: Kökü kuyruğa ekle
Kuyruk:1
Çıktı: (henüz yok)
Adım 1: Dequeue → 1 | Enqueue → sol:2, sağ:3
Kuyruk:23
Çıktı: [ 1 ]
Adım 2: Dequeue → 2 | Enqueue → sol:4, sağ:5
Kuyruk:345
Çıktı: [ 1, 2 ]
3 daha önce eklenmişti → 4,5'ten önce çıkacak (FIFO) → seviye sırası korunuyor!
Adım 3: Dequeue → 3 | Enqueue → sol:6, sağ:7
Kuyruk:4567
Çıktı: [ 1, 2, 3 ]
Seviye 1 tamamlandı ✓ Kuyrukta sadece Seviye 2 düğümleri kaldı
Adım 4: Dequeue → 4 | Çocuğu yok → enqueue yapılmaz
Kuyruk:567
Çıktı: [ 1, 2, 3, 4 ]
Adım 5: Dequeue → 5 | Çocuğu yok
Kuyruk:67
Çıktı: [ 1, 2, 3, 4, 5 ]
Adım 6: Dequeue → 6 | Çocuğu yok
Kuyruk:7
Çıktı: [ 1, 2, 3, 4, 5, 6 ]
Adım 7: Dequeue → 7 | Çocuğu yok → Kuyruk boş → BİTTİ ✓
Kuyruk:[ boş ]
Çıktı: [ 1, 2, 3, 4, 5, 6, 7 ] ✓
FIFO'nun büyüsü: Adım 2'de 3, kuyruğa 4 ve 5'ten önce eklenmişti. FIFO kuralı 3'ü önce çıkardı — böylece Seviye 1'deki tüm düğümler Seviye 2'den önce tamamlandı. Hiçbir özyineleme veya derinlik kontrolü gerekmedi — kuyruk yapısı bunu otomatik sağladı.
Genel ikili ağaçta (BST değil!) düğüm ekleme kuralı basittir: ağacı complete (eksiksiz) tutmak için yeni düğüm level-order'da ilk boş konuma eklenir. BFS ile ağacı dolaşıp sol veya sağ çocuğu NULL olan ilk düğümü buluruz.
Ekleme algoritması: 1. Ağaç boşsa → yeni düğüm kök olur. 2. Değilse → BFS ile dolaş. 3. Sol çocuğu NULL olan ilk düğümü bul → sol çocuk olarak ekle. 4. Sol dolu ama sağ NULL ise → sağ çocuk olarak ekle. 5. İkisi de doluysa → sonraki düğüme geç (BFS devam).
Complete yapı korundu: yeni seviyenin en solu dolduruldu
Ekleme Kodu
insert() — BFS ile ilk boş yeri bul
Node* insert(Node* root, int deger) {
Node* yeni = yeniDugum(deger);
if (!root) return yeni;
Queue q;
qInit(&q);
enqueue(&q, root);
while (!qEmpty(&q)) {
Node* cur = dequeue(&q);
if (!cur->left) {
cur->left = yeni; // ilk boş sol → eklereturn root;
} else {
enqueue(&q, cur->left);
}
if (!cur->right) {
cur->right = yeni; // ilk boş sağ → eklereturn root;
} else {
enqueue(&q, cur->right);
}
}
return root; // ulaşılmaz (güvenlik)
}
// Karmaşıklık: O(n) — en kötüde tüm düğümleri dolaşır
Düğüm Silme (Deletion)
İkili ağaçta düğüm silme, eklemeye göre çok daha karmaşıktır. Silinecek düğümün çocukları varsa, ağaç yapısı bozulmamalıdır. Genel ikili ağaçta (BST değil!) kullanılan strateji:
Silme stratejisi: 1. Silinecek düğümü BFS ile bul. 2. Ağacın en derin, en sağdaki düğümünü (deepest rightmost) BFS ile bul. 3. Silinecek düğümün verisini en derin düğümün verisiyle değiştir (swap). 4. En derin düğümü sil (yaprak olacağı için basitçe kaldır).
Bu strateji ağacı complete olmaya yakın tutar ve yapıyı korur.
Neden bu strateji? Silinecek düğüm iç düğüm (internal) olabilir — onu direkt kaldırmak çocuklarını öksüz bırakır. En derin sağdaki düğümle takas edersek, artık silinen düğüm yaprak olur → basitçe kaldırılır. Bu strateji BST sıralamasını korumaz (gerek yok — BST değiliz), ama ağaç yapısını sağlam tutar.
Silme Senaryosu — delete(2)
delete(2) — 4 Adımlık Silme İşlemi
① Mevcut ağaç — 2'yi silmek istiyoruz
② BFS ile en derin, en sağdaki düğümü bul → 7
BFS sırası: 1→2→3→4→5→6→7 (son çıkan = en derin sağdaki)
③ Swap: 2'nin verisini 7 ile değiştir
2 artık yaprak pozisyonunda → güvenle silinebilir
④ Yaprak düğümü (eski 7 pozisyonu) sil → free()
6 düğüm kaldı, 2'nin verisi gitti ama ağaç yapısı sağlam ✓
Silme Kodu
deleteNode() — en derin düğümle takas + sil
/* ── En derin, en sağdaki düğümü sil ── */voidenDerinSil(Node* root, Node* hedef) {
Queue q;
qInit(&q);
enqueue(&q, root);
while (!qEmpty(&q)) {
Node* cur = dequeue(&q);
// Hedefin ebeveynini bul ve bağını kesif (cur->left) {
if (cur->left == hedef) {
cur->left = NULL;
free(hedef);
return;
}
enqueue(&q, cur->left);
}
if (cur->right) {
if (cur->right == hedef) {
cur->right = NULL;
free(hedef);
return;
}
enqueue(&q, cur->right);
}
}
}
/* ── Düğüm silme ── */Node* deleteNode(Node* root, int deger) {
if (!root) returnNULL;
// Tek düğümlü ağaçif (!root->left && !root->right) {
if (root->data == deger) {
free(root);
returnNULL;
}
return root;
}
Queue q;
qInit(&q);
enqueue(&q, root);
Node* hedef = NULL; // silinecek düğümNode* enDerin = NULL; // en derin sağdakiwhile (!qEmpty(&q)) {
enDerin = dequeue(&q);
if (enDerin->data == deger)
hedef = enDerin;
if (enDerin->left) enqueue(&q, enDerin->left);
if (enDerin->right) enqueue(&q, enDerin->right);
}
// Döngü bittiğinde enDerin = son dequeue edilen = en derin sağdakiif (hedef) {
hedef->data = enDerin->data; // veriyi takas etenDerinSil(root, enDerin); // yaprak düğümü sil
}
return root;
}
// Karmaşıklık: O(n) — iki BFS geçişi
Ağacı Tamamen Silme (Bellek Temizleme)
Ağacın tüm düğümlerini free etmek için postorder kullanılır: önce çocukları sil, sonra kendini. Bu sıra kritiktir — preorder ile silmeye çalışırsanız çocuklara erişim kaybedersiniz.
agaciSil() — postorder ile bellek temizleme
voidagaciSil(Node* node) {
if (!node) return;
agaciSil(node->left); // 1. Sol alt ağacı silagaciSil(node->right); // 2. Sağ alt ağacı silfree(node); // 3. Kendini sil (postorder!)
}
// Silme sırası: 4→5→2→6→7→3→1 (postorder)
Silme özeti: Tek düğüm silme → en derin sağdakiyle takas + yaprak sil → O(n). Tüm ağacı silme → postorder traversal ile free → O(n). Her iki durumda da bellek sızıntısı oluşmaz ✓
Ağaç Yüksekliği Bulma
Ağaç yüksekliği, kökten en derin yaprağa kadar olan kenar sayısıdır. Özyinelemeli hesaplama mantığı: her düğümün yüksekliği = max(sol yükseklik, sağ yükseklik) + 1. NULL düğümün yüksekliği -1'dir (kenar sayısı tanımı).
Yükseklik algoritması (postorder mantığı): 1. NULL ise → -1 döndür (temel durum). 2. Sol yükseklik = height(left) 3. Sağ yükseklik = height(right) 4. max(sol, sağ) + 1 döndür.
n vs h vs w: n = toplam düğüm, h = yükseklik (dengeli: log n, dejenere: n), w = en geniş seviyedeki düğüm sayısı (perfect ağaçta: n/2). DFS alan = O(h), BFS alan = O(w).
Sonuç
İkili ağaç, hiyerarşik veri yapılarının temel taşıdır. Bu bölümde 10 temel operasyonu öğrendik: oluşturma, 4 dolaşma yöntemi (inorder, preorder, postorder, level-order), ekleme, silme ve 3 ölçüm (yükseklik, düğüm sayısı, yaprak sayısı). Özyinelemenin (recursion) ağaç operasyonlarındaki gücünü gördük — neredeyse tüm fonksiyonlar 5 satırın altında.
Tüm operasyonların O(n) olduğuna dikkat edin — genel ikili ağaç yapısı arama için optimize değildir. Bir sonraki bölümde İkili Arama Ağacı (BST)'nı tanıyacağız: sol < kök < sağ kuralı sayesinde arama, ekleme ve silme O(h)'ye düşer. Dengeli bir BST'de h = O(log n) — bu da 1.000.000 elemanda sadece ~20 adım demektir.
Bölüm 6.2 özeti: İkili ağaç = max 2 çocuk/düğüm. Dolaşma: preorder (kök-sol-sağ), inorder (sol-kök-sağ), postorder (sol-sağ-kök), level-order (BFS+kuyruk). Ekleme: BFS ile ilk boş yer. Silme: en derin sağdakiyla takas + yaprak sil. Ölçüm: yükseklik max(sol,sağ)+1, düğüm 1+sol+sağ, yaprak sol+sağ. Tüm operasyonlar O(n). Özyineleme = ağacın doğal dili.