10  İkili Ağaçlar — Bölüm 1

Düğüm + parent/left/right işaretçileri, kök/yaprak, derinlik vs yükseklik, örtük traversal sırası, O(h) işlemler (subtree_first/successor/insert_after/delete) ve BST özelliği (sequence vs set)

NotBölüm bilgisi

10.1 Bu Derste Ne Var?

Bugüne dek gördüğümüz yapıların hiçbiri her şeyi iyi yapmıyor: bağlı liste ortaya ekler ama ortaya \(O(n)\)’de ulaşır; dizi ortaya ulaşır ama eklerken kaydırır; hash tablosu find yapar ama find_prev/find_next’te kötüdür. Erik Demaine’in deyişiyle, ikili ağaçlar neredeyse her işlemi \(O(\log n)\)’de yapan “gördüğümüz en güçlü veri yapısıdır”.

Bu ders (Bölüm 1) tüm işlemleri \(O(h)\) (\(h\) = ağacın yüksekliği) yapar; bir sonraki ders \(h = O(\log n)\) garantisini ekler.

Üç temel kavram bu derste yan yana gelir:

  1. İkili ağaç yapısı — düğüm (item + parent/left/right), kök, yaprak, traversal (geziş) sırası.
  2. \(O(h)\) işlemler — subtree_first, successor, insert_after, delete; hepsi yükseklikle sınırlı.
  3. BST özelliği — sıralı anahtarlarla, ağaç ikili aramayı dinamik olarak destekler (find, find_prev/find_next).

“today we are doing some of the coolest data structures we will see in this class, maybe some of the coolest data structures ever — binary trees.” — Demaine, 0:18

flowchart TD
    A["Ders 9 (L6): İkili Ağaçlar — Bölüm 1"] --> S["İkili ağaç yapısı<br/>düğüm = item + parent/left/right"]
    S --> D2["Derinlik / yükseklik<br/>köke kenar / en derin yaprağa kenar"]
    A --> OP["O(h) dört işlem"]
    OP --> O1["subtree_first<br/>sola git"]
    OP --> O2["successor<br/>iki durum"]
    OP --> O3["insert_after<br/>successor.sol"]
    OP --> O4["delete<br/>item takası"]
    A --> TR["Traversal sırası ÖRTÜK<br/>sol → düğüm → sağ"]
    TR --> TR1["asla diziye yazılmaz<br/>kafamızda yaşar"]
    A --> SQ["Sequence vs Set"]
    SQ --> SQ1["sequence: traversal = sıra"]
    SQ --> SQ2["set: traversal = artan anahtar<br/>BST özelliği"]
    A --> Q["Açık soru: h nasıl küçük kalır?<br/>→ Ders 10: AVL (dengeli ağaç)"]

    classDef root fill:#fef3c7,stroke:#b45309,stroke-width:3px,color:#1f2937
    classDef branch fill:#f3f4f6,stroke:#374151,stroke-width:2px,color:#1f2937
    classDef leaf fill:#ffffff,stroke:#9ca3af,stroke-width:1px,color:#1f2937
    classDef open fill:#fde68a,stroke:#b45309,stroke-width:2.5px,color:#1f2937
    class A root
    class S,OP,TR,SQ branch
    class D2,O1,O2,O3,O4,TR1,SQ1,SQ2 leaf
    class Q open
Şekil 10.1: Ders 9’un (L6) kavram haritası: ikili ağaç yapısından (düğüm = item + parent/left/right) O(h) dört temel işleme (subtree_first → successor → insert_after → delete), in-order traversal sırasının ÖRTÜK temsiline (asla diziye yazılmaz) ve aynı ağacın iki arayüzüne — sequence (traversal = sıra) ve set (traversal = artan anahtar, BST özelliği). Geriye kalan tek soru, yüksekliği h’yi nasıl küçük (log n) tutarız — bu açık düğüm Ders 10’da AVL ile kapanır.
İpucuBuilder Notu — İki Dünyanın Köprüsü

İkili ağaç, şimdiye dek gördüğümüz iki dünyanın güçlü yanlarını dinamik olarak birleştirir: bağlı listenin esnek değiştirmesi + sıralı dizinin hızlı araması.

  • Geriye → Ders 2-5: ağaç, bağlı listenin (esnek ama yavaş erişim) ve sıralı dizinin (hızlı arama ama statik) güçlü yanlarını birleştirir — birinin zaafı ötekinin gücüdür.
  • Geriye → Ders 4 (ikili arama): BST’de find, sıralı dizideki ikili aramanın ağaç üzerindeki halidir — ama ekleme/silme artık kaydırma değil, \(O(h)\) işaretçi işi.
  • İleriye → veritabanı: B-tree / B+-tree indeksleri tam bu fikrin disk-dostu genellemesidir; çoğu SQL motorunun varsayılan indeksi bir dengeli ağaçtır (B-tree).
  • İleriye → Ders 10 (denge): bugün \(h\) herhangi bir şey olabilir (kötü ağaçta \(O(n)\)); sonraki ders AVL ile \(h = O(\log n)\) garanti eder.

Tek cümle: İkili ağaç, sıralı bir düzeni “kafadaki” traversal sırasıyla temsil edip, tüm işlemleri ağaç yüksekliği \(h\) ile sınırlar — yeter ki \(h\) küçük (\(\log n\)) kalsın.

10.2 Hedef: Tüm İşlemler O(log n)

İki arayüzü hatırla: dizi (sequence) — ortaya ekle/sil, \(i\). öğeye eriş; küme (set) — anahtarla ara, find_prev/find_next. Şimdiye dek hiçbir yapı bunların hepsini verimli yapmadı. İkili ağaçların hedefi: build/iterate dışındaki tüm işlemleri \(O(\log n)\) (uçlarda sabit-zamanlı bağlı liste/dinamik diziden bir log faktör geride, ama her yerde hızlı).

Bugün bu işlemleri \(O(h)\) (\(h\) = yükseklik) yapacağız; \(h\)’yi \(\log n\)’e bağlamak sonraki dersin işi. Şekil 10.2 bu “\(O(h)\) iyi mi kötü mü?” sorusunu somutlaştırır: aynı 7 öğe, zincir ağaçta \(h = 6\), dengeli ağaçta \(h = 2\).

Şekil 10.2: \(O(h)\) iyi mi kötü mü? → tamamen \(h\)’ye bağlı. İki panel AYNI 7 öğeyle (\(0..6\)) çizilir. Sol — zincir ağaç (yalnız sağ çocuklar, build_chain_tree(7)): bağlı liste gibi düz iner, \(h = 6 = n - 1\) (motor hesaplı, EXACT); tüm \(O(h)\) işlemler \(O(n)\)’e düşer. Sağ — dengeli ağaç (build_balanced_tree([0..6]), sıralı listeyi ortadan böl): yalnızca 3 seviye, \(h = 2 = \lfloor \log_2 7 \rfloor\) (motor hesaplı, EXACT). İki ağacın in-order traversal’ı AYNI \([0,1,2,3,4,5,6]\) — yalnız ŞEKİL farklı. Açık soru: \(h\)’yi kim küçük tutar? → Ders 10: AVL (kendini dengeleyen ağaç) bu düğümü kapatır.

10.3 İkili Ağaç Nedir?

Bir ikili ağaç, dairelerle çizdiğimiz düğümlerden (node) oluşur. Her düğüm bir item ve üç işaretçi tutar: node.parent, node.left, node.right. Çizimde çift yönlü (parent ↔︎ child) bağlar olduğundan oklar yerine düz çizgiler kullanılır.

  • Kök (root): parent’ı olmayan tek düğüm.
  • Yaprak (leaf): çocuğu olmayan düğüm.
  • Değişmez: node.left.parent == node (ve right için de) — parent, left/right işleminin tersidir.

“with binary trees, because we use two types of next pointers — left and right — we can build a tree, and trees in general can have logarithmic height.” — Demaine, 8:37

Şekil 10.3 bu anatomiyi tek bir örnek ağaçta toplar: düğüm yapısı, kök/yaprak ve derinlik ile yüksekliğin görsel ayrımı.

Şekil 10.3: İkili ağaç anatomisi (L6 §2/§4): deterministik örnek ağaç (\(D\) kök; in-order traversal \(A,B,C,D,E,F,G\); \(h = 2\)). Orta: 7 daire düğüm + düz çizgi kenarlar (ok yok — çift yönlü parent↔︎child bağı); kök \(D\) amber çerçeveli (parent yok), yapraklar \(A,C,E,G\) (çocuk yok). Sol yakın çekim: \(B\) düğümü = item + 3 işaretçi (parent yukarı, left/right aşağı). Sağ: iki yol kıyaslanır — \(A\) için derinlik (slate kesikli, \(A \to B \to D\), köke 2 kenar, \(d(A) = 2\)) ve kök \(D\) için yükseklik (amber, \(D \to F \to G\), en derin yaprağa 2 kenar, \(h = 2\)). Ağacın yüksekliği = kök yüksekliği = \(h\); bugünkü tüm işlemler \(O(h)\).

10.4 Neden İki İşaretçi?

Bağlı liste düğümünün tek “sonraki” işaretçisi vardır → yalnızca bir liste kurabilir, ortadaki düğümün derinliği lineerdir (oraya ulaşmak \(O(n)\)). İkili ağaç iki tür sonraki işaretçi (left, right) kullandığından bir ağaç kurabilir; ağaçlar logaritmik yükseklikte olabilir, böylece kökten herhangi bir düğüme yalnızca \(\log n\) adımda ulaşılır. İki dünyanın en iyisinin sırrı budur.

10.5 Tanımlar: Alt Ağaç, Derinlik, Yükseklik

  • Alt ağaç (subtree of x): \(x\) ve tüm torunları (descendants); \(x\) o alt ağacın köküdür.
  • Derinlik (depth): \(x\)’ten köke giden yoldaki kenar sayısı (\(x\)’in atalarının sayısı). Kökün derinliği 0.
  • Yükseklik (height): \(x\)’in alt ağacındaki en uzun aşağı yoldaki kenar sayısı (= alt ağaçtaki maksimum derinlik). Yaprakların yüksekliği 0.

“height of a node is the number of edges in the longest downward path.” — Demaine, 13:03

Ağacın yüksekliği = kökün yüksekliği = \(h\). Bugünkü tüm işlemler \(O(h)\) olacak; kötü bir ağaç (sadece sağ işaretçiler kullanılan, bağlı liste gibi) \(O(n)\) yüksekliğe sahip olabilir — bundan kaçınmak sonraki dersin konusu (bkz. Şekil 10.2).

10.6 Traversal (Geziş) Sırası

Ağaçta doğal bir düzen vardır: traversal sırası (in-order). Özyinelemeli tanım: her düğüm için, node.left alt ağacındaki düğümler \(x\)’ten önce, node.right alt ağacındakiler sonra gelir.

“the nodes in x.left are before x, and the nodes in x.right come after x.” — Demaine, 18:24

Geziş algoritması: sol alt ağacı gez → \(x\)’i çıkar → sağ alt ağacı gez (tüm ağaç için \(O(n)\)). Kritik: bu sıra asla açıkça hesaplanmaz — diziye yazılamaz (ortaya ekleme \(O(n)\) olurdu). Sıra her zaman örtüktür, “kafamızdadır”; ağaç yapısı onu örtük olarak kodlar.

“the traversal order is never explicitly computed… it’s always implicit, in our heads.” — Demaine, 30:37

Şekil 10.4, örnek ağacın bu örtük sırasını (altta hayalet bir dizi olarak) ve düğümlerden o diziye inen kesikli okları gösterir.

Şekil 10.4: In-order traversal: ağacın ÖRTÜK sırası (L6 §5; sol → düğüm → sağ). Üstte örnek ağaç (7 düğüm, kök \(D\)); her düğümün üst-sağında amber sıra rozeti (\(A{\to}1, \dots, G{\to}7\)). Altta hayalet bir dizi şeridi \([A][B][C][D][E][F][G]\) — kesikli çerçeveli, çünkü bu dizi ASLA gerçekten kurulmaz; her düğümden kendi hücresine ince kesikli slate ok iner. Vurgu (amber kutu): sıra ÖRTÜKTÜR, işaretçilerde yaşar — diziye yazmak ortaya eklemeyi \(O(n)\) yapardı, ağacın tüm amacı tam da bundan kaçınmaktır (Demaine 30:37).
İpucuBuilder Notu — Örtük Düzen, Range Scan ve Lazy Temsil

Traversal sırasının asla diziye yazılmaması, bir veri yapısı tasarım ilkesinin saf hâlidir: pahalı bir gösterimi açıkça tutmak yerine, ucuz güncellenebilir bir yapıyla örtük temsil et.

  • DB indeks BETWEEN / range scan: Bir B-tree indeksinde WHERE x BETWEEN a AND b sorgusu, tam olarak in-order traversal’ın bir parçasını yürür — a’yı bul, successor zinciriyle b’ye kadar git. Sıralı sonuç “kafadaki” düzenden okunur, ayrıca tutulan bir sıralı dizi yoktur.
  • Sıralı yineleme (ordered iteration): Bir set’i artan sırada gezmek (SELECT ... ORDER BY indeks üzerinden) ağaçta doğal \(O(n)\) traversal’dır; ekstra sıralama maliyeti yoktur çünkü düzen yapıda zaten kodludur.
  • Lazy temsil genel ilkesi: “Materialize etme, gerektiğinde üret” — sıralı dizi (eager) yerine ağaç (lazy) seçmek, ortaya ekleme/silmeyi \(O(n)\)’den \(O(h)\)’ye düşürür. Aynı sezgi: streaming iterator’lar, generator’lar, sanal DOM diff’leri.

Tek cümle: Örtük traversal, “sıralı görünmek” ile “sıralı dizi tutmak” arasındaki farktır — birincisi \(O(h)\)’de güncellenir, ikincisi \(O(n)\)’de.

10.7 subtree_first ve successor

İki temel sorgu (ikisi de \(O(h)\)):

  • subtree_first(node): Bir alt ağacın traversal sırasındaki ilk düğümü. Tanım gereği sol her zaman önce gelir → mümkün olduğunca sola git (node = node.left), düşmeden bir önceki düğümde dur.
  • successor(node): Tüm ağacın traversal sırasında node’dan sonraki düğüm. İki durum:

“all of these operations are going to be order h.” — Demaine, 32:02

Çalışılan Örnek — successor. Durum 1: node’un sağ çocuğu varsa, successor = subtree_first(node.right) (sağ alt ağacın en solundaki düğüm — node’dan sonraki her şeyin ilki). Durum 2: sağ çocuk yoksa, sola-dal yukarı çık: node = node.parent, ta ki bir “sol dalı” yukarı çıkana dek (yani önceki düğüm parent’ın sol çocuğuysa). O parent, successor’dur. Sezgi: sağ alt ağaç yoksa, node’dan sonra gelen ilk şey, onu sol-tarafında bırakan en yakın atadır.

Örnek ağaçta (kök \(D\)): successor(B) = C Durum 1’dir (\(B\)’nin sağ çocuğu \(C\) var); successor(C) = D Durum 2’dir (\(C\)’nin sağ çocuğu yok, yol \(C \to B \to D\)). Şekil 10.5 bu iki durumu yan yana gösterir.

Şekil 10.5: Traversal successor — iki durum, her ikisi de \(O(h)\) (L6 §6, İMZA figür; örnek ağaç \(A..G\), \(h = 2\)). Sol panel — Durum 1, successor(B): \(B\)’nin sağ çocuğu VAR → successor sağ alt ağacın ilkidir: subtree_first(node.right); bir adım sağa (\(C\)), sonra sola sonuna dek (yok) → dur. Yol \(B \to C\) (amber), sonuç \(C\). Sağ panel — Durum 2, successor(C): \(C\)’nin sağ çocuğu YOK → sola-dal yukarı çık: \(C\), \(B\)’nin sağ çocuğu (devam et), \(B\), \(D\)’nin SOL çocuğu (DUR). Yol \(C \to B \to D\) (amber, yukarı), sonuç \(D\). Sezgi: sağ alt ağaç yoksa, successor node’u sol tarafında bırakan en yakın atadır.

10.8 insert_after

insert_after(node, new): yeni bir düğümü, traversal sırasında node’dan hemen sonraya ekle. İki durum (her ikisi de successor’a indirgenir, \(O(h)\)):

Çalışılan Örnek — insert_after. Durum 1: node’un sağ çocuğu yoksa, new’i doğrudan node.right yap. Durum 2: sağ çocuk varsa, node’un successor’ını bul (subtree_first(node.right)); bu successor’ın sol çocuğu olmadığı garantilidir (çünkü sağa bir kez, sonra sola sonuna dek giderek bulundu). O hâlde new’i successor’ın sol çocuğu yap. Yeni traversal sırası: node → new → eski successor → … Sabit-zaman iş + \(O(h)\) successor = \(O(h)\).

Örnek ağaçta insert_after(C, X) Durum 1’dir (\(C\).right boş → \(X\), \(C\).right olur, traversal ...C, X, D...); insert_after(D, Y) Durum 2’dir (\(D\).right dolu (F), successor \(= E\), \(Y\)\(E\).left, traversal ...D, Y, E...). Şekil 10.6 iki durumu yan yana gösterir.

Şekil 10.6: insert_after — sabit işaretçi işi + \(O(h)\) successor arama \(= O(h)\) (L6 §7; örnek ağaç \(A..G\)). Sol panel — Durum 1, insert_after(C, X): \(C\).right BOŞ → \(X\) (amber yeni düğüm) doğrudan \(C\).right olur; yeni traversal \(\dots C, X, D \dots\) Sağ panel — Durum 2, insert_after(D, Y): \(D\).right DOLU (\(F\)) → successor$(D) = $ subtree_first(D.right) \(= E\) (yol \(D \to F \to E\), kesikli amber). Bu successor’ın sol çocuğu OLAMAZ (sağa bir kez + sola sonuna dek inilerek bulundu) → \(Y\), \(E\)’nin SOL çocuğu olur; yeni traversal \(\dots D, Y, E \dots\) İki durumda da yalnız birkaç işaretçi değişir, maliyet successor aramasıyla sınırlı: \(O(h)\).

10.9 delete

delete(node): bir düğümü traversal sırasından çıkar. Düğüm bir yaprak ise basitçe parent’tan kopar (taban durum). Değilse, düğümü ağaçta aşağı taşıyarak yaprağa indirip silmek için takas yapılır:

  • Sol çocuk varsa: predecessor (sıralamada önceki) sol alt ağaçtadır (daha aşağıda). node’un item’ını predecessor’ın item’ıyla takas et, sonra predecessor’ı özyinelemeli sil.
  • Sağ çocuk varsa (sol yoksa): simetrik — successor ile takas et, successor’ı sil.

Her takasta düğüm bir seviye aşağı iner; toplam iş ağaç yüksekliğiyle orantılı → \(O(h)\). (Burada ilk kez düğümlerin içeriğini değiştiriyoruz, düğümün kendisini değil.)

“trees will not preserve connections — that’s just the name of the game.” — Demaine, 47:02

Şekil 10.7 somut bir örnekte (build_bst([16,8,24,4,12,2,6,10,14,13]), kök 16’yı sil) bu takas zincirini üç kare hâlinde gösterir: \(16 \leftrightarrow 14\), sonra \(16 \leftrightarrow 13\), sonra yaprakta kopar; son traversal \([2,4,6,8,10,12,13,14,24]\).

Şekil 10.7: İç düğüm silme = item TAKASI zinciri (L6 §8, İMZA figür; build_bst([16,8,24,4,12,2,6,10,14,13]), \(h = 4\), kök \(16\) silinir). Düğüm DAİRELERİ yapısal konumda sabit kalır; yalnız İÇERİK aşağı iner (Demaine 47:02). Kare 1: \(16\) (kök) amber dolgu “SİL”; predecessor yolu \(16 \to 8 \to 12 \to 14\) (kesikli amber); çift-başlı takas oku \(16 \leftrightarrow 14\). Kare 2: \(14\) köke çıktı, \(16\) bir seviye indi (\(14\)’ün eski yeri); \(16\)’nın sol çocuğu \(13\) ile ikinci takas \(16 \leftrightarrow 13\). Kare 3: \(16\) artık yaprakta → parent’tan koparılır (✕). Altta son in-order şeridi \([2,4,6,8,10,12,13,14,24]\) (\(16\) yok ✓). Her takas item’ı bir seviye indirir → toplam \(\le h = 4\) takas \(= O(h)\).
İpucuBuilder Notu — İşaretçi Cerrahisi ve Korunan Değişmez

delete, ağaç işlemlerinin ortak iskeletini açığa çıkarır: her işlem birkaç işaretçiyi keser/bağlar, ve doğruluğu korunan bir değişmezle (invariant) kanıtlanır.

  • Parent-child invariant: Her düğümde node.left.parent is node (ve right için). insert_after yeni bir bağ kurarken bu değişmezi hem yukarı hem aşağı tamamlamalı (new.parent = ... unutulursa successor yukarı-yürüyüşü kırılır). delete’te item takası bu değişmezi hiç bozmaz — çünkü daireler yerinde kalır, yalnız içerik değişir; bu yüzden takas “güvenli” bir tekniktir.
  • Her işlem bir ispat: subtree_first/successor/insert_after/delete doğruluğu, “traversal sırası korunur” + “parent-child tutarlı” değişmezlerinin her adımda sağlandığını göstermekle ispatlanır. Bir builder için ders: işaretçi koduna güven, ispatla gelir — testte bu iki değişmezi assert et (tree_traversal == beklenen, check_parent_child).
  • Genel ilke: Bağlı yapılarda (linked list, ağaç, graf) her mutasyon bir “cerrahi”dir; bug’lar neredeyse her zaman yarım kalmış bir bağ (dangling/half-updated pointer) yüzündendir. Değişmezi yaz, her işlemden sonra kontrol et.

Tek cümle: İşaretçi cerrahisinin güvenliği, her kesimden sonra aynı değişmezi geri kurmaktan gelir — ağaç kodunun doğruluğu, hız değil, değişmez disiplinidir.

10.10 Sequence ve Set Olarak Ağaç

Aynı ağaç iki arayüzü de gerçekleştirir, yalnızca traversal sırasının anlamı değişir:

  • Dizi (sequence): traversal sırası = sequence sırası (\(x_0, x_1, \dots, x_{n-1}\)).
  • Küme (set): traversal sırası = artan anahtar sırası.

Set durumunda BST özelliği (binary search tree property) doğar: her düğümde, sol alt ağacın tüm anahtarları \(<\) düğüm \(<\) sağ alt ağacın tüm anahtarları.

“this is something called the binary search tree property, BST property.” — Demaine, 49:11

find(k): kökten başla, \(k\)’yı düğümle karşılaştır; küçükse sola, büyükse sağa in — ikili aramanın ağaç hâli, \(O(h)\). find_prev/find_next: ağaçtan düşersen (\(k\) yoksa), son denenen yön sana önceki ya da sonrakini verir; diğerini predecessor/successor ile bulursun. (Diziyi tam gerçekleştirmek biraz daha iş ister — sonraki ders.)

İpucuBuilder Notu — BST = Dinamik İkili Arama (B-tree/SQL İndeksin Atası)

BST özelliği, Ders 4’teki sıralı dizi ikili aramasını dinamik hâle getirir: arama hâlâ \(O(h)\), ama artık ekleme/silme de \(O(h)\) — kaydırma yok.

  • Dinamik ikili arama: Sıralı dizi find’i \(O(\log n)\) yapar ama insert/delete \(O(n)\) (kaydırma). BST, find + find_prev/next + insert + delete’in hepsini \(O(h)\) yapar; \(h = O(\log n)\) sağlanırsa (Ders 10), sıralı dizinin tüm avantajı + dinamiklik elde edilir.
  • B-tree / B+-tree = disk-dostu genelleme: Her düğümde 2 değil \(B\) çocuk tutarak ağaç sığlaşır (\(h \approx \log_B n\)); bu, bir disk bloğu = bir düğüm eşlemesiyle disk erişimini minimize eder. PostgreSQL, MySQL InnoDB, SQLite — hepsinin varsayılan indeksi bir B+-tree’dir; “varsayılan SQL indeksi bir dengeli ağaçtır” sözünün kökü budur (hash/GIN gibi özel indeks türleri ayrı uzmanlıklardır).
  • find_prev/find_next = en yakın komşu: Hash tablosunun yapamadığı “en yakın daha büyük/küçük anahtar” sorgusu (range query, BETWEEN, autocomplete) ağacın doğal yaptığı şeydir — bu yüzden veritabanı sıralı taramaları hash değil ağaç indeksi ister.

Tek cümle: BST, ikili aramayı statik diziden kurtarıp dinamik yapar; B-tree onu diske taşır — modern her veritabanı indeksinin atası bu derstir.

10.11 Bu Dersin Özeti

  1. İkili ağaç: düğüm (item + parent/left/right); kök parent’sız, yaprak çocuksuz.
  2. İki işaretçi (left/right) logaritmik yükseklik sağlar; bağlı listenin lineer derinliğini aşar.
  3. Derinlik (köke kenar) ve yükseklik (en derin yaprağa kenar); ağacın yüksekliği \(h\).
  4. Traversal sırası (in-order): sol → düğüm → sağ; örtük, asla diziye yazılmaz.
  5. subtree_first / successor / insert_after / delete: hepsi \(O(h)\).
  6. Sequence: traversal = sıra; Set: traversal = artan anahtar (BST özelliği).
  7. find / find_prev / find_next: BST’de ikili arama, \(O(h)\).
ÖnemliTek Bir Cümle

İkili ağaç, sıralı bir düzeni örtük traversal sırasıyla temsil eder ve tüm işlemleri ağaç yüksekliği \(h\) ile sınırlar; geriye tek soru kalır — \(h\)’yi nasıl küçük (\(\log n\)) tutarız?

10.12 Kontrol Soruları

Cevap: Traversal sırasını bir dizide tutmak, ortaya ekleme/silmede tüm öğeleri kaydırmayı gerektirir → \(O(n)\). İkili ağacın tüm amacı bu düzeni örtük (işaretçilerle) tutmaktır; böylece insert_after, delete gibi işlemler yalnızca birkaç işaretçi değiştirerek \(O(h)\) zamanda düzeni günceller. Sıra “kafadadır”; bilgisayarda yalnızca düğümler ve işaretçiler saklanır.

Cevap: Durum 1 — sağ çocuk var: node’dan sonra gelen her şey sağ alt ağaçtadır; bunların ilki (en solu) successor’dır → subtree_first(node.right). Durum 2 — sağ çocuk yok: node’dan sonra gelecek hiçbir şey altında değil; sola-dal yukarı çıkıp, node’u sol tarafında bırakan en yakın atayı buluruz (o atanın sol alt ağacı node’u içerir, kendisi sonra gelir). İkisi de \(O(h)\).

Cevap: Yaprak olmayan bir düğümü doğrudan silmek ağacı koparır (çocuklarına giden işaretçiler boşa düşer). Bunun yerine, düğümün item’ını predecessor/successor’ın item’ıyla takas ederiz; bu, “silinecek” item’ı ağaçta bir seviye aşağı taşır. Tekrarlayarak item bir yaprağa iner ve yaprak güvenle silinir. Düğüm daireleri yerinde kalır, yalnızca içerikleri değişir; toplam iş \(O(h)\).

Cevap: Sıralı dizi find’i \(O(\log n)\) (ikili arama) yapar ama insert/delete \(O(n)\)’dir (kaydırma). İkili ağaç (BST), find’i ve find_prev/next’i \(O(h)\) yapar ve insert/delete’i de \(O(h)\)’de tutar — düzeni kaydırma olmadan, işaretçilerle günceller. \(h = O(\log n)\) sağlanırsa (sonraki ders), tüm işlemler \(O(\log n)\) olur; sıralı dizinin statik kısıtı kalkar.

10.13 Egzersizler

Egzersiz 1. Verilen bir ikili ağaç için traversal (in-order) sırasını elle çıkar; sonra subtree_first(kök) ve successor ile baştan sona dolaşarak aynı sırayı yeniden üret.

Egzersiz 2. subtree_last ve predecessor işlemlerini, subtree_first/successor’ın simetriği olarak tanımla (sağ ↔︎ sol).

Egzersiz 3. insert_after Durum 2’de, successor’ın neden sol çocuğu olamayacağını tek cümlede ispatla (subtree_first’in tanımına dayan).

Egzersiz 4. Python’da bir BST düğüm sınıfı yaz ve find(k) ile find_next(k)’yi gerçekleştir:

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

def find(node, k):
    while node and node.key != k:
        node = node.left if k < node.key else node.right
    return node

Egzersiz 5. delete’te, hem sol hem sağ çocuğu olan bir düğüm için neden “tek bir durum” (sadece sol VEYA sadece sağ) yeterli olur? Demaine’in “iki durumdan biri beni mutlu eder” gözlemini açıkla.

10.14 Sonraki Ders İçin Hazırlık

Ders 10 (L7): İkili Ağaçlar — Bölüm 2

Erik Demaine ile, bugün açık bıraktığımız soruyu kapatıyoruz: ağacın yüksekliği \(h\)’yi \(O(\log n)\) olmaya nasıl zorlarız? AVL ağaçları (dengeli ikili ağaçlar) ve rotasyon işlemiyle, tüm \(O(h)\) işlemleri \(O(\log n)\) garantisine çeviriyoruz. (Not: ders akışında araya Problem Oturumu 4 girer.)

UyarıDers 10 Öncesi Yapılacak
  • Bu dersin egzersizlerini, özellikle Egzersiz 4’ü (BST find) çöz.
  • Dört \(O(h)\) işlemini (subtree_first, successor, insert_after, delete) ezberden anlat.
  • Ana cümleyi tekrar oku: “Geriye tek soru kalır — \(h\)’yi nasıl küçük tutarız?”

10.15 Anahtar Kavramlar (Cheat Sheet)

Kavram Tanım Sayfada
İkili ağaç (düğüm) item + parent/left/right; kök parent’sız, yaprak çocuksuz Böl. 2
Derinlik / yükseklik Köke kenar / en derin yaprağa kenar; ağaç yüksekliği \(h\) Böl. 4
Traversal sırası In-order: sol → düğüm → sağ; örtük Böl. 5
subtree_first Alt ağacın ilki; sola git; \(O(h)\) Böl. 6
successor Sıradaki düğüm; sağ varsa subtree_first, yoksa sola-dal yukarı; \(O(h)\) Böl. 6
insert_after Sağ yoksa node.right; varsa successor’ın sol çocuğu; \(O(h)\) Böl. 7
delete Yaprak→kopar; değilse pred/succ item takası + özyinele; \(O(h)\) Böl. 8
BST özelliği sol anahtarlar \(<\) düğüm \(<\) sağ anahtarlar; find \(O(h)\) Böl. 9

10.16 Builder ve OMSCS Bağlantıları

İpucu6 köprü

Bu ders, “sıralı düzeni nasıl ucuza dinamik tutarız” sezgisini kurar — köprülerin özeti:

  1. BST / dengeli ağaç → veritabanı indeksleri (B-tree, B+-tree): varsayılan SQL indeksi ve dosya sistemi dizinleri bu fikrin disk-dostu hâli.
  2. Traversal sırası → sıralı yineleme: bir set’i sıralı gezmek (range query, BETWEEN) ağaçta doğal \(O(n)\) traversal.
  3. find_prev / find_next → “en yakın komşu” sorguları: hash tablosunun yapamadığı, ağacın doğal yaptığı şey.
  4. \(O(h) \to O(\log n)\) → denge disiplini: bir veri yapısının garantisi, en kötü durum yüksekliğini sınırlamaya bağlıdır (sonraki ders).
  5. İşaretçi manipülasyonu + invariant → her ağaç işleminin doğruluğu, korunan bir değişmezle (BST özelliği, parent-child tutarlılığı) ispatlanır.
  6. Örtük düzen → genel ilke: pahalı bir gösterimi (sıralı dizi) açıkça tutmak yerine, ucuz güncellenebilir bir yapıyla (ağaç) örtük temsil etmek.

ÖnemliTek bir şey alıp gideceksen

İkili ağaç, “sıralı dizi gibi hızlı ara ama bağlı liste gibi esnek değiştir” hayalini gerçekleştirir — düzeni işaretçilerle örtük tutarak. Tüm işlemler \(O(h)\)’dir; tek görev, \(h\)’yi \(O(\log n)\)’de tutmak (bir sonraki ders: AVL).