Bölüm 10: Arama Ağaçları

Veri Yapıları
İkili arama ağacıArama ve eklemeSilmeO(h) analizi Denge ve döndürmeAVL ağaçlarıKırmızı-siyah ağaçlarSplay ağaçlarıB-ağacı ve B+ ağacı
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

BST işlemleri

Arama, ekleme ve silme işlemlerini ikili arama ağacında uygulamak.

Denge gereksinimi

Ağaç yüksekliğinin işlem maliyetlerine etkisini açıklamak.

Dengeleme

AVL döndürmelerini ve kırmızı-siyah ağaç kurallarını yorumlamak.

Karşılaştırma

BST, AVL, kırmızı-siyah, splay ve B-ağaçlarını kullanım amacına göre seçmek.

Arama Ağaçları

Arama Ağacı (Search Tree)

  • Her düğüm bir öğeyi temsil eder; her öğenin bir anahtarı (key) vardır.
  • Öğeler anahtarlarına göre sıralı bir düzende saklanır.
  • Arama, kökten başlayıp her düğümde tek bir dala inerek yapılır.
  • Maliyet, ağacın yüksekliği h ile orantılıdır: O(h).
  • n düğümlü ağaçta h, en iyi ≈ log2 n, en kötü n − 1 olur.
6 2 1 4 3 8derinlik 0derinlik 1derinlik 2derinlik 3
Arama ağaçlarının bütün hikâyesi: işlemler O(h) sürer; o hâlde h'yi küçük tutmak (dengeli ağaç) işlemleri O(log n)'e indirir.

Arama Ağaçları

Temel İşlemler

Arama (Search)Verilen anahtar ağaçta varsa true, yoksa false döndürür.ara(kok, 5) → true
Ekleme (Insertion)Yeni öğeyi sıralama düzenini bozmadan uygun yaprak konumuna ekler.ekle(kok, 7)
Silme (Deletion)Verilen anahtara sahip öğeyi çıkarır ve düzeni korur.sil(kok, 40)
Gezinme (Traversal)Öğeleri belirli bir sırayla gezinir; kök ortada (inorder) gezinme anahtarları sıralı verir.inorder(kok) → 1 2 3 4 6 8

Ek olarak: en küçük / en büyük anahtar, bir anahtarın ardılı / öncülü, aralık sorguları. Hepsi O(h).

Arama Ağaçları

Arama Ağacı Türleri

İkili Arama Ağacı (BST)Her düğümün anahtarı, sol alt ağaçtaki anahtarlardan büyük, sağ alt ağaçtakilerden küçüktür. Denge garantisi yoktur.63148
AVL AğacıHer düğümde iki alt ağacın yükseklik farkı (denge faktörü) en fazla 1'dir. Döndürmelerle dengelenir.40201030605070
Kırmızı-Siyah Ağaç (Red-Black)Düğüm renkleri ve kurallarıyla yüksekliği en fazla 2·log2(n+1) ile sınırlar.201051530
Splay AğacıErişilen düğümü döndürmelerle köke taşır; işlem başına amortize O(log n).53148← son erişilen

Arama Ağaçları

İkili Arama Ağacı (Binary Search Tree)

Her düğüm x için: sol alt ağaçtaki tüm anahtarlar < x.anahtar < sağ alt ağaçtaki tüm anahtarlar.

6 2 1 4 3 8 6 2 1 4 3 7 8✗
  • Özellik yalnızca çocuklar için değil, bütün alt ağaç için geçerlidir: sağdaki ağaçta 7 > 4 doğru, ama 7, 6'nın solunda olamaz.
  • Tekrarlayan anahtarlar ya reddedilir ya da tutarlı biçimde tek tarafa (ör. sağa) konur.

Arama Ağaçları

En Küçük, En Büyük ve Sıralı Gezinme

6 2 1 4 3 8min: hep solamax: hep sağa
Kök ortada (sol → kök → sağ) gezinme anahtarları sıralı verir:
1 2 3 4 6 8
// Ön koşul: dugum != null
IkiliAgacDugum<E> minDeger(
        IkiliAgacDugum<E> dugum) {
    while (dugum.solCocuk != null)
        dugum = dugum.solCocuk; // sola in
    return dugum;  // en küçük
}
// maxDeger: aynısı, sagCocuk ile

void inorder(IkiliAgacDugum<E> d) {
    if (d == null) return;
    inorder(d.solCocuk);  // sol
    System.out.print(d.veri + " ");
    inorder(d.sagCocuk);  // sağ
}

Arama Ağaçları · Kodu adım adım çalıştır

Kod İzleme: minDeger ve maxDeger

IkiliAgacDugum<E> minDeger(
        IkiliAgacDugum<E> dugum) {
    while (dugum.solCocuk != null)
        dugum = dugum.solCocuk; // sola in
    return dugum;  // en küçük
}
IkiliAgacDugum<E> maxDeger(
        IkiliAgacDugum<E> dugum) {
    while (dugum.sagCocuk != null)
        dugum = dugum.sagCocuk; // sağa in
    return dugum;  // en büyük
}
6dugum21438

minDeger(kok): dugum kökten (6) başlar. Ön koşul: ağaç boş değil.

6dugum21438

dugum.solCocuk = 2 ≠ null → döngü devam.

62dugum1438

dugum = dugum.solCocuk → 2. Sola iniyoruz.

62dugum1438

dugum.solCocuk = 1 ≠ null → döngü devam.

621dugum438

dugum = dugum.solCocuk → 1. Sola iniyoruz.

621dugum438

dugum.solCocuk == null → döngü biter. 1'in sol çocuğu yok.

621dugum438

return 1: ağacın en küçük anahtarı. (3 daha derinde ama 4'ün solunda; en küçük hep en soldadır.)

6dugum21438

maxDeger(kok): dugum kökten (6) başlar. Ön koşul: ağaç boş değil.

6dugum21438

dugum.sagCocuk = 8 ≠ null → döngü devam.

621438dugum

dugum = dugum.sagCocuk → 8. Sağa iniyoruz.

621438dugum

dugum.sagCocuk == null → döngü biter. 8'in sağ çocuğu yok.

621438dugum

return 8: ağacın en büyük anahtarı. Karşılaştırma yok, yalnızca bağ takibi: O(h).

Arama Ağaçları · Kodu adım adım çalıştır

Kod İzleme: inorder(kok)

6d21438

inorder(6): d ≠ null. Önce sol alt ağaç: inorder(2) çağrılır; 6 bekler.

62d1438

inorder(2): d ≠ null. Önce sol alt ağaç: inorder(1) çağrılır; 2 bekler.

621d438

inorder(1): sol çocuk yok → inorder(null) satır 2'de hemen döner.

621d438

Sol taraf bitti → 1 yazdırılır. (Sol alt ağaçtaki her anahtar 1'den küçüktü.)

621d438

Sağ çocuk yok → çerçeve kapanır; çağıran inorder(2) kaldığı yerden devam eder.

62d1438

Sol taraf bitti → 2 yazdırılır. (Sol alt ağaçtaki her anahtar 2'den küçüktü.)

62d1438

Şimdi sağ alt ağaç: inorder(4).

6214d38

inorder(4): d ≠ null. Önce sol alt ağaç: inorder(3) çağrılır; 4 bekler.

62143d8

inorder(3): sol çocuk yok → inorder(null) satır 2'de hemen döner.

62143d8

Sol taraf bitti → 3 yazdırılır. (Sol alt ağaçtaki her anahtar 3'ten küçüktü.)

62143d8

Sağ çocuk yok → çerçeve kapanır; çağıran inorder(4) kaldığı yerden devam eder.

6214d38

Sol taraf bitti → 4 yazdırılır. (Sol alt ağaçtaki her anahtar 4'ten küçüktü.)

6214d38

Sağ çocuk yok → çerçeve kapanır; çağıran inorder(2) kaldığı yerden devam eder.

62d1438

2'nin sağ alt ağacı da bitti → inorder(2) kapanır; çağıran kaldığı yerden devam eder.

6d21438

Sol taraf bitti → 6 yazdırılır. (Sol alt ağaçtaki her anahtar 6'dan küçüktü.)

6d21438

Şimdi sağ alt ağaç: inorder(8).

621438d

inorder(8): sol çocuk yok → inorder(null) satır 2'de hemen döner.

621438d

Sol taraf bitti → 8 yazdırılır. (Sol alt ağaçtaki her anahtar 8'den küçüktü.)

621438d

Sağ çocuk yok → çerçeve kapanır; çağıran inorder(6) kaldığı yerden devam eder.

621438

Tüm çağrılar bitti; çıktı sıralı: 1 2 3 4 6 8. Her düğüm bir kez yazıldı → O(n).

void inorder(IkiliAgacDugum<E> d) {
    if (d == null) return;
    inorder(d.solCocuk);  // sol
    System.out.print(d.veri + " ");
    inorder(d.sagCocuk);  // sağ
}

Arama Ağaçları

Ağaç Düğümü Yapısı

solverisagsol çocuksağ çocuk
int anahtarlı
class AgacDugumu {
    int veri;
    AgacDugumu sol, sag;

    AgacDugumu(int veri) {
        this.veri = veri;
        this.sol = null;
        this.sag = null;
    }
}
generic
class IkiliAgacDugum<E extends Comparable<E>> {
    E veri;                    // nesne
    IkiliAgacDugum<E> solCocuk; // sol çocuk
    IkiliAgacDugum<E> sagCocuk; // sağ çocuk

    IkiliAgacDugum(E veri) {
        this.veri = veri;
        // çocuklar başlangıçta null
    }
}

Anahtarlar compareTo ile karşılaştırılır; bu yüzden E, Comparable<E> olmalıdır.

BST İşlemleri

Öğe Arama

  • Kökten başla; anahtar eşitse bulundu.
  • Küçükse sol, büyükse sağ alt ağaçta devam et.
  • null'a ulaşırsan anahtar ağaçta yoktur.
6 4 2 5 8 7 95 < 65 > 4
boolean ara(IkiliAgacDugum<E> kok, E veri) {
    if (kok == null)
        return false;            // yok
    int k = veri.compareTo(kok.veri);
    if (k == 0)
        return true;             // bulundu
    else if (k < 0)
        return ara(kok.solCocuk, veri);
    else
        return ara(kok.sagCocuk, veri);
}
Nesneleri == ile değil compareTo (veya equals) ile karşılaştır: == yalnızca referansların aynı olup olmadığına bakar.

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: ara(kok, 5) ve ara(kok, 10)

boolean ara(IkiliAgacDugum<E> kok, E veri) {
    if (kok == null)
        return false;            // yok
    int k = veri.compareTo(kok.veri);
    if (k == 0)
        return true;             // bulundu
    else if (k < 0)
        return ara(kok.solCocuk, veri);
    else
        return ara(kok.sagCocuk, veri);
}
6kok425879

ara(kok, 5) çağrıldı; kok kökü (6) gösterir. kok ≠ null. k = compareTo = -1 çünkü 5 < 6.

6kok425879

k < 0 → sol alt ağaçta ara: ara(kok.solCocuk, 5) yeni bir çerçeve açar (kok = 4).

64kok25879

kok ≠ null. k = compareTo = 1 çünkü 5 > 4.

64kok25879

k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 5) yeni bir çerçeve açar (kok = 5).

6425kok879

kok ≠ null. k = compareTo = 0 çünkü 5 = 5.

6425kok879

k == 0 → anahtar bulundu: return true. Bu çerçeve kapanır.

64kok25879

ara(5, 5) çağrısı true döndürdü; 4'teki çerçeve bu değeri aynen return eder.

6kok425879

ara(4, 5) çağrısı true döndürdü; 6'daki çerçeve bu değeri aynen return eder.

6425879

main sonucu alır: true. 3 düğümle karşılaştırıldı = bulunan düğümün derinliği + 1.

6kok425879

ara(kok, 10) çağrıldı; kok kökü (6) gösterir. kok ≠ null. k = compareTo = 1 çünkü 10 > 6.

6kok425879

k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = 8).

64258kok79

kok ≠ null. k = compareTo = 1 çünkü 10 > 8.

64258kok79

k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = 9).

6425879kok

kok ≠ null. k = compareTo = 1 çünkü 10 > 9.

6425879kok

k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = null).

nullkok6425879

kok == null: 9'un sağ çocuğu yok, yani 10 ağaçta yok → return false.

6425879kok

ara(null, 10) çağrısı false döndürdü; 9'daki çerçeve bu değeri aynen return eder.

64258kok79

ara(9, 10) çağrısı false döndürdü; 8'deki çerçeve bu değeri aynen return eder.

6kok425879

ara(8, 10) çağrısı false döndürdü; 6'daki çerçeve bu değeri aynen return eder.

6425879

Sonuç false: 3 düğüm ziyaret edildi. En fazla h + 1 düğüm → O(h).

BST İşlemleri

Öğe Ekleme

  • Aranıyormuş gibi in; düştüğün null konuma yeni yaprak olarak ekle.
  • Yeni düğüm her zaman yaprak olur; O(h).
6 2 1 4 3 5 8ekle 5
IkiliAgacDugum<E> ekle(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null)
        return new IkiliAgacDugum<>(veri);
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = ekle(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = ekle(kok.sagCocuk, veri);
    // k == 0: anahtar zaten var, eklenmez
    return kok;
}
// kullanım: kok = ekle(kok, 5);
Özyinelemeli çağrının sonucu çocuk bağına geri atanır; böylece yeni düğüm, null olan bağın yerine bağlanır.

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: ekle(kok, 5)

IkiliAgacDugum<E> ekle(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null)
        return new IkiliAgacDugum<>(veri);
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = ekle(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = ekle(kok.sagCocuk, veri);
    // k == 0: anahtar zaten var, eklenmez
    return kok;
}
// kullanım: kok = ekle(kok, 5);
621438

main, kok = ekle(kok, 5) çağırır. Ekleme de aramada olduğu gibi kökten başlar.

6kok21438

kok ≠ null (6). k = -1 çünkü 5 < 6.

6kok21438

Sol alt ağaca in: ekle(kok.solCocuk, 5); kok = 2. Dönen değer kok.solCocuk'a atanacak.

62kok1438

kok ≠ null (2). k = 1 çünkü 5 > 2.

62kok1438

Sağ alt ağaca in: ekle(kok.sagCocuk, 5); kok = 4. Dönen değer kok.sagCocuk'a atanacak.

6214kok38

kok ≠ null (4). k = 1 çünkü 5 > 4.

6214kok38

Sağ alt ağaca in: ekle(kok.sagCocuk, 5); kok = null. Dönen değer kok.sagCocuk'a atanacak.

6214385yeni

kok == null: boş yere ulaşıldı. new ile düğüm 5 oluşturulur ve döndürülür — henüz hiçbir bağ onu göstermiyor.

6214kok358

4'ün çerçevesine dönüldü: kok.sagCocuk = düğüm(5). Null olan bağın yerine yeni düğüm bağlandı (turuncu).

6214kok358

return kok: 4, kendi ebeveynine (2) geri döndürülür.

62kok14358

2'de kok.sagCocuk = düğüm(4): bağ zaten 4'ü gösteriyordu, değişmez. Sonra return 2.

6kok214358

6'da kok.solCocuk = düğüm(2) (aynı bağ), sonra return 6.

6kok214358

main: kok = 6 (kök değişmedi). Yeni düğüm hep yaprak olur; yol uzunluğu kadar çağrı → O(h).

BST İşlemleri

Ardıl (Successor) ve Öncül (Predecessor)

1) Sağ alt ağaç varsaardıl = sağ alt ağacın en küçüğü: bir sağa, sonra hep sola.6x425879
2) Sağ alt ağaç yoksaardıl = solundan geçtiğimiz en yakın ata (5 → 4 → 6: 5, 6'nın sol alt ağacında).6425x879
İşlemAnlamıJava TreeMapYukarıdaki ağaçta
ardılx'ten büyük en küçük anahtarhigherKey(x)ardil(5) = 6
öncülx'ten küçük en büyük anahtarlowerKey(x)oncul(6) = 5
tavan (ceiling)≥ x olan en küçük anahtarceilingKey(x)tavan(3) = 4
taban (floor)≤ x olan en büyük anahtarfloorKey(x)taban(3) = 2

Hepsi kökten tek bir yol izler: O(h). İki çocuklu düğüm silinirken yerine konan düğüm, ardılıdır.

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: ardil(kok, 5) ve ardil(kok, 6)

6kok425879

ardil(kok, 5): henüz aday yok (aday = null); kökten (6) başla.

6aday4kok25879

5 < 6 → 6 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in.

6aday425kok879

5 ≥ 4 → 4 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in.

nullkok6aday425879

5 ≥ 5 → 5 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in. kok = null oldu.

6aday425879

Döngü bitti (kok == null) → return düğüm(6): ardil(5) = 6.

6kok425879

İkinci çağrı: ardil(kok, 6): henüz aday yok (aday = null); kökten (6) başla.

64258kok79

6 ≥ 6 → 6 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in.

64258aday7kok9

6 < 8 → 8 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in.

nullkok642587aday9

6 < 7 → 7 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in. kok = null oldu.

642587aday9

Döngü bitti (kok == null) → return düğüm(7): ardil(6) = 7.

AgacDugumu ardil(AgacDugumu kok, int x) {
    AgacDugumu aday = null; // x'ten büyük en küçük
    while (kok != null) {
        if (x < kok.veri) {
            aday = kok;     // kok bir aday
            kok = kok.sol;  // daha küçüğü solda
        } else
            kok = kok.sag;  // x'ten büyükler sağda
    }
    return aday;
}

BST İşlemleri

Öğe Silme: Üç Durum

Önce silinecek düğüm aranır (O(h)). Sonra çocuk sayısına göre:

1. YaprakÇocuğu yok. Ebeveynin ilgili bağı null yapılır.sil 70
2. Tek çocukDüğüm atlanır: ebeveyn doğrudan tek çocuğa bağlanır.sil 45 · sil 60
3. İki çocukDeğer, kök ortada ardıl (sağın en küçüğü) ya da öncül (solun en büyüğü) ile değiştirilir.sil 40
50 40 30 45 42 60 70

Örnek ağaç

BST İşlemleri

Silme: Yaprak ve Tek Çocuk

50 40 30 45 42 60 70

Yaprak: 70'in çocuğu yok. Ebeveyni 60'ın sağ bağı null yapılır.

50 40 30 45 42 60

70 silindi; ağacın geri kalanı değişmedi.

50 40 30 45 42 60 70

Tek çocuk (sol): 45'in yalnızca sol çocuğu 42 var. (Orijinal ağaçtan başlıyoruz.)

50 40 30 42 60 70

42, 45'in yerine 40'ın sağ çocuğu olur: ebeveyn doğrudan toruna bağlanır.

50 40 30 45 42 60 70

Tek çocuk (sağ): 60'ın yalnızca sağ çocuğu 70 var.

50 40 30 45 42 70

70, 60'ın yerine 50'nin sağ çocuğu olur. Sıralama düzeni korunur.

BST İşlemleri

Silme: İki Çocuklu Düğüm

50 40 30 45 42 60 70

İki çocuk: 40'ın hem solu (30) hem sağı (45) var; doğrudan çıkarılamaz.

50 40 30 45 42 60 70

İnorder ardıl = sağ alt ağacın en küçüğü: 45'e geç, sonra hep sola → 42.

50 42 30 45 42 60 70

Ardılın değeri (42), 40'ın düğümüne kopyalanır. Şimdi eski 42 fazladan duruyor.

50 42 30 45 60 70

Sağ alt ağaçtan eski 42 silinir; ardılın sol çocuğu olmadığı için bu hep Durum 1 veya 2'dir.

50 30 45 42 60 70

Alternatif: kök ortada öncül (sol alt ağacın en büyüğü = 30) kullanılabilir. İkisi de geçerli BST verir.

BST İşlemleri

Silme Kodu

IkiliAgacDugum<E> sil(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null) return null;   // yok
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = sil(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = sil(kok.sagCocuk, veri);
    else if (kok.solCocuk == null)
        return kok.sagCocuk;  // yaprak/sağ çocuk
    else if (kok.sagCocuk == null)
        return kok.solCocuk;  // yalnız sol çocuk
    else {                    // iki çocuk
        kok.veri = minDeger(kok.sagCocuk).veri;
        kok.sagCocuk = sil(kok.sagCocuk, kok.veri);
    }
    return kok;
}
Dönen değer ebeveynin bağına atanır: tek çocuk ya da null dönünce düğüm ağaçtan kopar. Kullanım: kok = sil(kok, 40); (kök silinirse yeni kök bu atamayla güncellenir).

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: sil(kok, 70) — yaprak

IkiliAgacDugum<E> sil(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null) return null;   // yok
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = sil(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = sil(kok.sagCocuk, veri);
    else if (kok.solCocuk == null)
        return kok.sagCocuk;  // yaprak/sağ çocuk
    else if (kok.sagCocuk == null)
        return kok.solCocuk;  // yalnız sol çocuk
    else {                    // iki çocuk
        kok.veri = minDeger(kok.sagCocuk).veri;
        kok.sagCocuk = sil(kok.sagCocuk, kok.veri);
    }
    return kok;
}
50403045426070

main, kok = sil(kok, 70) çağırır. Önce silinecek düğüm aranır.

50kok403045426070

kok ≠ null. k = 1 çünkü 70 > 50.

50kok403045426070

Sağ alt ağaçta sil: sil(kok.sagCocuk, 70). Dönen değer kok.sagCocuk'a atanacak.

504030454260kok70

kok ≠ null. k = 1 çünkü 70 > 60.

504030454260kok70

Sağ alt ağaçta sil: sil(kok.sagCocuk, 70). Dönen değer kok.sagCocuk'a atanacak.

50403045426070kok

kok ≠ null. k = 0 çünkü 70 = 70.

50403045426070kok

k = 0 → silinecek düğüm bulundu. Sol çocuk yok → return kok.sagCocuk = null. (Yaprak: null döner.)

504030454260kok70

Değişen bağ: kok.sagCocuk = null. Silinen düğüme artık hiçbir bağ yok; çöp toplayıcı temizler.

504030454260kok70

return kok: 60 kendi ebeveynine döndürülür.

50kok4030454260

kok.sagCocuk = düğüm(60): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).

504030454260

main: kok = 50. 70 silindi: yaprak olduğu için yalnızca 60'ın sağ bağı null oldu.

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: sil(kok, 45) — tek çocuk

IkiliAgacDugum<E> sil(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null) return null;   // yok
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = sil(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = sil(kok.sagCocuk, veri);
    else if (kok.solCocuk == null)
        return kok.sagCocuk;  // yaprak/sağ çocuk
    else if (kok.sagCocuk == null)
        return kok.solCocuk;  // yalnız sol çocuk
    else {                    // iki çocuk
        kok.veri = minDeger(kok.sagCocuk).veri;
        kok.sagCocuk = sil(kok.sagCocuk, kok.veri);
    }
    return kok;
}
50403045426070

main, kok = sil(kok, 45) çağırır. Önce silinecek düğüm aranır.

50kok403045426070

kok ≠ null. k = -1 çünkü 45 < 50.

50kok403045426070

Sol alt ağaçta sil: sil(kok.solCocuk, 45). Dönen değer kok.solCocuk'a atanacak.

5040kok3045426070

kok ≠ null. k = 1 çünkü 45 > 40.

5040kok3045426070

Sağ alt ağaçta sil: sil(kok.sagCocuk, 45). Dönen değer kok.sagCocuk'a atanacak.

50403045kok426070

kok ≠ null. k = 0 çünkü 45 = 45.

50403045kok426070

k = 0 → bulundu. Sol çocuk var, sağ çocuk yok → return kok.solCocuk = düğüm(42): tek çocuk ebeveyne verilir.

5040kok3042607045

Değişen bağ: kok.sagCocuk = 42. Ebeveyn, silinen düğümü atlayıp doğrudan onun çocuğuna bağlandı.

5040kok3042607045

return kok: 40 kendi ebeveynine döndürülür.

50kok4030426070

kok.solCocuk = düğüm(40): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).

504030426070

main: kok = 50. 45 silindi: tek çocuğu 42, 40'ın sağ çocuğu oldu. İnorder sıra korunur.

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: sil(kok, 40) — iki çocuk

IkiliAgacDugum<E> sil(
        IkiliAgacDugum<E> kok, E veri) {
    if (kok == null) return null;   // yok
    int k = veri.compareTo(kok.veri);
    if (k < 0)
        kok.solCocuk = sil(kok.solCocuk, veri);
    else if (k > 0)
        kok.sagCocuk = sil(kok.sagCocuk, veri);
    else if (kok.solCocuk == null)
        return kok.sagCocuk;  // yaprak/sağ çocuk
    else if (kok.sagCocuk == null)
        return kok.solCocuk;  // yalnız sol çocuk
    else {                    // iki çocuk
        kok.veri = minDeger(kok.sagCocuk).veri;
        kok.sagCocuk = sil(kok.sagCocuk, kok.veri);
    }
    return kok;
}
50403045426070

main, kok = sil(kok, 40) çağırır. Önce silinecek düğüm aranır.

50kok403045426070

kok ≠ null. k = -1 çünkü 40 < 50.

50kok403045426070

Sol alt ağaçta sil: sil(kok.solCocuk, 40). Dönen değer kok.solCocuk'a atanacak.

5040kok3045426070

kok ≠ null. k = 0 çünkü 40 = 40.

5040kok3045426070

k = 0 → bulundu, ama iki çocuğu var (30 ve 45): doğrudan çıkarılamaz.

5040kok304542ardıl6070

minDeger(kok.sagCocuk): sağa bir adım (45), sonra hep sola → ardıl = 42 (sağ alt ağacın en küçüğü).

5042kok3045426070

kok.veri = 42: ardılın değeri silinecek düğüme kopyalanır. Şimdi 42 iki kez var; alttakini silmeliyiz.

5042kok3045426070

sil(kok.sagCocuk, 42): eski 42 sağ alt ağaçtan silinir. Ardılın sol çocuğu olmadığından bu hep yaprak/tek çocuk durumudur.

50423045kok426070

kok ≠ null. k = -1 çünkü 42 < 45.

50423045kok426070

Sol alt ağaçta sil: sil(kok.solCocuk, 42). Dönen değer kok.solCocuk'a atanacak.

5042304542kok6070

kok ≠ null. k = 0 çünkü 42 = 42.

5042304542kok6070

k = 0 → silinecek düğüm bulundu. Sol çocuk yok → return kok.sagCocuk = null. (Yaprak: null döner.)

50423045kok607042

Değişen bağ: kok.solCocuk = null. Silinen düğüme artık hiçbir bağ yok; çöp toplayıcı temizler.

50423045kok607042

return kok: 45 kendi ebeveynine döndürülür.

5042kok30456070

kok.sagCocuk = düğüm(45) (aynı bağ), return kok. Silinen değer 40'ın yerini 42 aldı.

50kok4230456070

kok.solCocuk = düğüm(42): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).

504230456070

main: kok = 50. 40 silindi: yerine ardılı 42 geçti; düğüm sayısı azaldı, BST özelliği korundu. Maliyet O(h).

BST İşlemleri

İkili Arama Ağacı İşlemlerinin Analizi

  • Arama, ekleme ve silmenin maliyeti, erişilen en derin düğümün derinliğiyle orantılıdır: O(h).
4 2 1 3 6 5 7dengeli: h = 2 ≈ log₂ n 1 2 3 4 5devrik (zincir): h = n − 1
DurumYükseklik hArama / Ekleme / Silmen = 106 için adım
Dengeli (en iyi)≈ log2 nO(log n)≈ 20
Rastgele sıra (ortalama)≈ 2,99 log2 nO(log n)≈ 28 (ortalama)
Devrik (en kötü, sıralı girdi)n − 1O(n)106

Rastgele sırada bir düğümün ortalama derinliği ≈ 2 ln n ≈ 1,39 log2 n’dir (n = 106 için ≈ 28 adım); beklenen yükseklik ise ≈ 4,31 ln n ≈ 2,99 log2 n’dir (≈ 60).

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: aralık sorgusu aralik(kok, 5, 10)

842657121014lo = 5, hi = 10

aralik(8): 5 < 8 → solda aralığa giren anahtar olabilir: aralik(k.sol).

842657121014lo = 5, hi = 10

aralik(4): 5 < 4 yanlış → sol alt ağaçtaki her anahtar < 4 ≤ lo: sol alt ağaç budanır (hiç ziyaret edilmez).

842657121014lo = 5, hi = 10

Sol alt ağaç budandı (4 ≤ lo = 5), çağrı yapılmadı; aralik(4) sürer. 4 aralık dışında, yazdırılmaz. 4 < 10 → sağa da bak.

842657121014lo = 5, hi = 10

aralik(6): 5 < 6 → solda aralığa giren anahtar olabilir: aralik(k.sol).

842657121014lo = 5, hi = 10

aralik(5): 5 < 5 yanlış → sol alt ağaçtaki her anahtar < 5 ≤ lo: sol alt ağaç budanır (hiç ziyaret edilmez).

842657121014lo = 5, hi = 10

Sol alt ağaç budandı (5 ≤ lo = 5), çağrı yapılmadı; aralik(5) sürer. 5 ∈ [5, 10] → yazdır. 5 < 10 → sağa da bak.

null842657121014lo = 5, hi = 10

aralik(null): boş alt ağaç → hemen return.

842657121014lo = 5, hi = 10

Sol taraf bitti, aralik(6)'ya dönüldü. 6 ∈ [5, 10] → yazdır. 6 < 10 → sağa da bak.

842657121014lo = 5, hi = 10

aralik(7): 5 < 7 → solda aralığa giren anahtar olabilir: aralik(k.sol).

null842657121014lo = 5, hi = 10

aralik(null): boş alt ağaç → hemen return.

842657121014lo = 5, hi = 10

Sol taraf bitti, aralik(7)'ye dönüldü. 7 ∈ [5, 10] → yazdır. 7 < 10 → sağa da bak.

null842657121014lo = 5, hi = 10

aralik(null): boş alt ağaç → hemen return.

842657121014lo = 5, hi = 10

Sol taraf bitti, aralik(8)'e dönüldü. 8 ∈ [5, 10] → yazdır. 8 < 10 → sağa da bak.

842657121014lo = 5, hi = 10

aralik(12): 5 < 12 → solda aralığa giren anahtar olabilir: aralik(k.sol).

842657121014lo = 5, hi = 10

aralik(10): 5 < 10 → solda aralığa giren anahtar olabilir: aralik(k.sol).

null842657121014lo = 5, hi = 10

aralik(null): boş alt ağaç → hemen return.

842657121014lo = 5, hi = 10

Sol taraf bitti, aralik(10)'a dönüldü. 10 ∈ [5, 10] → yazdır. 10 ≥ 10 → sağ alt ağaç budanır.

842657121014lo = 5, hi = 10

Sol taraf bitti, aralik(12)'ye dönüldü. 12 aralık dışında, yazdırılmaz. 12 ≥ 10 → sağ alt ağaç budanır.

842657121014lo = 5, hi = 10

Bitti. Çıktı sıralı (kök ortada sırası): 5 6 7 8 10. 2 ve 14 hiç ziyaret edilmedi (soluk). Maliyet O(h + s), s: yazdırılan anahtar sayısı.

void aralik(AgacDugumu k, int lo, int hi) {
    if (k == null) return;
    if (lo < k.veri)           // solda aday var
        aralik(k.sol, lo, hi);
    if (lo <= k.veri && k.veri <= hi)
        System.out.print(k.veri + " ");
    if (k.veri < hi)           // sağda aday var
        aralik(k.sag, lo, hi);
}

BST İşlemleri · Kodu adım adım çalıştır

Kod İzleme: bir ağaç BST mi? bstMi()

6(−∞, ∞)214378

bstMi(6): 6 ∈ (−∞, ∞) ✓. Sol alt ağaç için üst sınır 6 olur: bstMi(k.sol, −∞, 6).

62(−∞, 6)14378

bstMi(2): 2 ∈ (−∞, 6) ✓. Sol alt ağaç için üst sınır 2 olur: bstMi(k.sol, −∞, 2).

621(−∞, 2)4378✓

bstMi(1): 1 ∈ (−∞, 2) ✓. Yaprak: iki çocuğu da null → ikisi de true döner → return true.

62(−∞, 6)14378✓

Sol taraf true. Sağ alt ağaç için alt sınır 2 olur: bstMi(k.sag, 2, 6).

6214(2, 6)378✓

bstMi(4): 4 ∈ (2, 6) ✓. Sol alt ağaç için üst sınır 4 olur: bstMi(k.sol, 2, 4).

62143(2, 4)78✓✓

bstMi(3): 3 ∈ (2, 4) ✓. Yaprak: iki çocuğu da null → ikisi de true döner → return true.

6214(2, 6)378✓✓

Sol taraf true. Sağ alt ağaç için alt sınır 4 olur: bstMi(k.sag, 4, 6).

621437(4, 6)8✓✓✗

bstMi(7), izinli aralık (4, 6): 7 ≥ 6 → return false. 7, 4'ün sağında olduğu için 4'ten büyük olmalı (✓), ama 6'nın sol alt ağacında olduğu için 6'dan küçük olmalıydı.

6214(2, 6)378✓✓✗✗

Sağdan false geldi → bstMi(4) = true && false = false.

62(−∞, 6)14378✓✓✗✗✗

Sağdan false geldi → bstMi(2) = true && false = false.

6(−∞, ∞)214378✓✓✗✗✗✗

Sol sonuç false → && kısa devre yapar: sağ alt ağaç hiç denetlenmez, bstMi(6) false döner.

6214378✓✓✗✗✗✗

Sonuç false: ağaç BST değil. Yalnızca ebeveyn–çocuk çiftlerine bakan kontrol (4 < 7 ✓) bu ağaca yanlışlıkla true derdi; aralığı aşağı taşımak şart. Maliyet O(n).

boolean bstMi(AgacDugumu k, long min, long max) {
    if (k == null) return true;
    if (k.veri <= min || k.veri >= max)
        return false;          // aralık dışında
    return bstMi(k.sol, min, k.veri)
        && bstMi(k.sag, k.veri, max);
}
// ilk çağrı: bstMi(kok, Long.MIN_VALUE,
//                  Long.MAX_VALUE)

BST İşlemleri

Uygulama: Öğrenci Kayıtları

Anahtar olarak öğrenci numarası kullanılır; düğüm, öğrenci nesnesinin tamamını taşır.

public class Ogrenci {
    int ogrenciNo;     // anahtar
    String adSoyad;

    public Ogrenci(int ogrenciNo,
                   String adSoyad) {
        this.ogrenciNo = ogrenciNo;
        this.adSoyad = adSoyad;
    }
}
public class OgrenciDugum {
    Ogrenci ogr;           // veri
    OgrenciDugum solCocuk;
    OgrenciDugum sagCocuk;

    public OgrenciDugum(Ogrenci ogr) {
        this.ogr = ogr;
        // çocuklar başlangıçta null
    }
}
Aynı fikir Java'daki TreeMap<Integer, Ogrenci> ile hazır gelir: anahtar → değer eşlemesi, içeride dengeli bir arama ağacı (kırmızı-siyah).

BST İşlemleri

Öğrenci Ekleme ve Arama

OgrenciDugum ekle(OgrenciDugum kok, Ogrenci ogr) {
    if (kok == null) return new OgrenciDugum(ogr);
    if (ogr.ogrenciNo < kok.ogr.ogrenciNo)
        kok.solCocuk = ekle(kok.solCocuk, ogr);   // soldan devam
    else if (ogr.ogrenciNo > kok.ogr.ogrenciNo)
        kok.sagCocuk = ekle(kok.sagCocuk, ogr);   // sağdan devam
    return kok;                                   // aynı numara eklenmez
}
Ogrenci ara(OgrenciDugum kok, int numara) {
    if (kok == null) return null;                // kayıt yok
    if (numara == kok.ogr.ogrenciNo) return kok.ogr; // bulundu
    if (numara < kok.ogr.ogrenciNo)
        return ara(kok.solCocuk, numara);         // soldan devam
    return ara(kok.sagCocuk, numara);             // sağdan devam
}

ara, boolean yerine öğrenci nesnesini döndürür: bulunursa ad-soyad gibi bilgilere de erişilir. int anahtar olduğu için == ve < doğrudan kullanılabilir.

Denge ve AVL

Denge İhtiyacı

  • BST'nin şekli ekleme sırasına bağlıdır: 1, 2, 3, 4 sırayla eklenirse ağaç bir bağlı listeye dönüşür.
  • Aynı anahtarlar, farklı yapılarla daha kısa bir ağaçta tutulabilir (sağda iki seçenek).
1 2 3 4 2 1 3 4 3 1 2 4h = 3h = 2h = 2
Dengeli (self-balancing) arama ağaçları, her ekleme ve silmeden sonra yapıyı küçük yerel değişikliklerle (döndürme, renk değiştirme) düzelterek h = O(log n) garantisi verir.

Denge ve AVL

Dengeleme Örneği

Soldaki ağaçta 9 → 14 → 12 ve 76 → 54 → 72 → 67 kolları uzamış. Bu alt ağaçlar yeniden düzenlenince (turuncu yeni alt ağaç kökleri) aynı 11 anahtar daha kısa bir ağaçta durur:

50 17 9 14 12 23 19 76 54 72 67 50 17 12 9 14 23 19 72 54 67 76yükseklik 4yükseklik 3 · tüm |bf| ≤ 1
  • Yeniden düzenleme sırasında kök ortada sıra değişmez: 9 12 14 17 19 23 50 54 67 72 76.
  • Ağacı her seferinde baştan kurmak O(n) olurdu; AVL ve kırmızı-siyah ağaçlar bunu yalnızca eklenen/silinen yol üzerinde, O(log n) sürede yapar.

Denge ve AVL

AVL Ağacı

  • Adelson-Velsky ve Landis (1962): ilk dengeli BST.
  • Denge faktörü: bf(x) = h(sol) − h(sağ). Boş alt ağacın yüksekliği −1.
  • AVL koşulu: her düğümde bf ∈ {−1, 0, +1}.
  • Sonuç: h < 1,44 log2(n + 2) → tüm işlemler O(log n).
  • Her düğüm yüksekliğini saklar; ekleme/silmeden sonra köke doğru yukarı çıkılırken bf kontrol edilir.
Nasıl okunur?Düğümlerin sağ üstündeki sayı bf'dir. Pozitif: sol ağır, negatif: sağ ağır. |bf| = 2 olan ilk (en alttaki) düğüm döndürülür.
300 200 100 250 40−1 500 302 202 101 50 400AVL ✓AVL değil: 20 ve 30'da |bf| = 2

Denge ve AVL

AVL Yüksekliği Neden O(log n)?

Soru: yüksekliği h olan bir AVL ağacında en az kaç düğüm vardır? En seyrek AVL ağacında her düğümün iki alt ağacının yükseklik farkı tam 1'dir.

10N(0) = 1

h = 0: tek düğüm. N(0) = 1.

2110N(0) = 1N(1) = 2

h = 1: kök + tek çocuk. N(1) = 2.

31211040N(0) = 1N(1) = 2N(2) = 4

h = 2: kök (turuncu) + yüksekliği 1 olan en seyrek ağaç (sol) + yüksekliği 0 olan en seyrek ağaç (sağ). N(2) = 1 + N(1) + N(0) = 1 + 2 + 1 = 4.

51312110407160N(0) = 1N(1) = 2N(2) = 4N(3) = 7

h = 3: kök (turuncu) + yüksekliği 2 olan en seyrek ağaç (sol) + yüksekliği 1 olan en seyrek ağaç (sağ). N(3) = 1 + N(2) + N(1) = 1 + 4 + 2 = 7.

815131211040716011110190120N(0) = 1N(1) = 2N(2) = 4N(3) = 7N(4) = 12

h = 4: kök (turuncu) + yüksekliği 3 olan en seyrek ağaç (sol) + yüksekliği 2 olan en seyrek ağaç (sağ). N(4) = 1 + N(3) + N(2) = 1 + 7 + 4 = 12. Sayılar Fibonacci gibi büyür: N(h) = F(h+3) − 1.

N(h) = 1 + N(h−1) + N(h−2) > 2·N(h−2) → N(h) > 2h/2 → h < 2 log2 n. Fibonacci ile daha sıkı sınır: h < 1,44 log2(n + 2).

Denge ve AVL

Döndürme (Rotation)

T3T1T2 y x sağa döndür(y) T1T2T3 x y kök ortada sıra her iki tarafta aynı: T1 · x · T2 · y · T3

Sağa döndürme (sol ağır y): x yukarı çıkar, y x'in sağ çocuğu olur; x'in sağ alt ağacı T2, y'nin soluna geçer.

T1T2T3 x y sola döndür(x) T3T1T2 y x kök ortada sıra her iki tarafta aynı: T1 · x · T2 · y · T3

Sola döndürme (sağ ağır x): simetrik. Yalnızca 3 bağ değişir (kalın çizgiler) → O(1).

Döndürme BST özelliğini korur (kök ortada sıra aynı kalır), ama alt ağaçların yüksekliklerini değiştirir: bir taraf bir düzey kısalır, diğeri uzar.

Denge ve AVL

Tek Döndürmeler: LL ve RR

302201100

LL: 10, sol çocuğun (20) soluna eklendi. bf(30) = 2 ✗ ve bf(20) = 1: iki düğüm de aynı yöne (sola) eğik.

200100300

30'da sağa döndür: 20 yukarı çıkar, 30 onun sağ çocuğu olur (turuncu bağ). Tüm bf = 0.

10−220−1300

RR: 30, sağ çocuğun (20) sağına eklendi. bf(10) = −2 ✗ ve bf(20) = −1: ikisi de sağa eğik.

200100300

10'da sola döndür: 20 yukarı çıkar, 10 onun sol çocuğu olur. Tüm bf = 0.

Düğümlerin sağ üstündeki sayı bf'dir. LL durumunda sağa, RR durumunda sola tek döndürme yapılır; turuncu bağ, döndürmede değişen bağdır.

Denge ve AVL

Çift Döndürmeler: LR ve RL

30210−1200

LR: 20, sol çocuğun (10) sağına eklendi. bf(30) = 2 ama bf(10) = −1: zıt yöne eğik (üçgen). Tek sağa döndürme yine üçgen bırakır.

302201100

1. döndürme: 10'da sola döndür → 20 yukarı; üçgen düz çizgiye (LL) döndü. bf(30) hâlâ 2.

200100300

2. döndürme: 30'da sağa döndür → ortanca anahtar 20 kök oldu; tüm bf = 0.

10−2301200

RL: 20, sağ çocuğun (30) soluna eklendi. bf(10) = −2, bf(30) = 1: üçgen.

10−220−1300

1. döndürme: 30'da sağa döndür → düz çizgi (RR). bf(10) hâlâ −2.

200100300

2. döndürme: 10'da sola döndür → 20 kök; tüm bf = 0.

LR: önce çocukta sola, sonra dengesiz düğümde sağa. RL: önce çocukta sağa, sonra sola.

Denge ve AVL

AVL Döndürme ve Dengeleme Kodu

int h(AvlDugum d) {
    return d == null ? -1 : d.yukseklik;
}
int bf(AvlDugum d) {
    return h(d.sol) - h(d.sag);
}
void guncelle(AvlDugum d) {
    d.yukseklik = 1 +
        Math.max(h(d.sol), h(d.sag));
}
AvlDugum sagaDondur(AvlDugum y) {
    AvlDugum x = y.sol;
    y.sol = x.sag;  // T2 → y.sol
    x.sag = y;      // y → x.sag
    guncelle(y);    // önce alttaki
    guncelle(x);
    return x;       // yeni kök
}
AvlDugum dengele(AvlDugum d) {
    guncelle(d);
    if (bf(d) > 1) {       // sol ağır
        if (bf(d.sol) < 0)  // LR
            d.sol = solaDondur(d.sol);
        return sagaDondur(d); // LL
    }
    if (bf(d) < -1) {      // sağ ağır
        if (bf(d.sag) > 0)  // RL
            d.sag = sagaDondur(d.sag);
        return solaDondur(d); // RR
    }
    return d;
}
// solaDondur: sol ↔ sag simetriği
// ekle/sil BST gibi; dönüşte:
//   return dengele(kok);

Özyineleme geri dönerken yol üzerindeki her düğümde dengele çağrılır; silmede birden fazla düğümde döndürme gerekebilir (en fazla O(log n)).

Denge ve AVL · Kodu adım adım çalıştır

Kod İzleme: sagaDondur(40) — LL durumu

AvlDugum sagaDondur(AvlDugum y) {
    AvlDugum x = y.sol;
    y.sol = x.sag;  // T2 → y.sol
    x.sag = y;      // y → x.sag
    guncelle(y);    // önce alttaki
    guncelle(x);
    return x;       // yeni kök
}
void guncelle(AvlDugum d) {
    d.yukseklik = 1 +
        Math.max(h(d.sol), h(d.sag));
}
402h=3201h=2101h=150h=0300h=0500h=0

5 eklendi; dengele(40) bf = 2 buldu, sol çocuğu 20'de bf = 1 → LL. sagaDondur(y) çağrılır.

40h=3y20h=2x10h=15h=030h=050h=0

x = y.sol: yukarı çıkacak çocuk 20. Henüz hiçbir bağ değişmedi.

40h=3y20h=2x10h=15h=030h=050h=0

y.sol = x.sag: 40'ın sol bağı artık T2'yi (30) gösterir. 20 ağaçtan koptu; ona yalnızca x değişkeni ulaşıyor.

40h=3y20h=2x10h=15h=030h=050h=0

x.sag = y: x.sag: 30 → 40. Artık 40, 20'nin sağ çocuğu (turuncu ok yukarı doğru).

40h=1y20h=2x10h=15h=030h=050h=0

guncelle(y): 40.yukseklik = 1 + max(0, 0) = 1 (önceden 3). Önce alttaki düğüm güncellenir; x'in yüksekliği buna bağlı.

40h=1y20h=2x10h=15h=030h=050h=0

guncelle(x): 20.yukseklik = 1 + max(1, 1) = 2 (değişmedi, ama yeniden hesaplanmalı).

200h=2x101h=150h=0400h=1y300h=0500h=0

return x: 20 bu alt ağacın yeni kökü (aynı bağlar, yeniden çizildi). Tüm bf ∈ {−1, 0, 1}; kök ortada sıra değişmedi. Metotta 2 bağ değişti, 3. bağı (ebeveynden) çağıran atar → O(1).

Denge ve AVL · Kodu adım adım çalıştır

Kod İzleme: solaDondur(20) — RR durumu

AvlDugum solaDondur(AvlDugum x) {
    AvlDugum y = x.sag;
    x.sag = y.sol;  // T2 → x.sag
    y.sol = x;      // x → y.sol
    guncelle(x);    // önce alttaki
    guncelle(y);
    return y;       // yeni kök
}
void guncelle(AvlDugum d) {
    d.yukseklik = 1 +
        Math.max(h(d.sol), h(d.sag));
}
20−2h=3100h=040−1h=2300h=050−1h=1600h=0

60 eklendi; dengele(20) bf = −2 buldu, sağ çocuğu 40'ta bf = −1 → RR. solaDondur(x) çağrılır.

20h=3x10h=040h=2y30h=050h=160h=0

y = x.sag: yukarı çıkacak çocuk 40. Henüz hiçbir bağ değişmedi.

20h=3x10h=040h=2y30h=050h=160h=0

x.sag = y.sol: 20'nin sağ bağı artık T2'yi (30) gösterir. 40 ağaçtan koptu; ona yalnızca y değişkeni ulaşıyor.

20h=3x10h=040h=2y30h=050h=160h=0

y.sol = x: y.sol: 30 → 20. Artık 20, 40'ın sol çocuğu (turuncu ok yukarı doğru).

20h=1x10h=040h=2y30h=050h=160h=0

guncelle(x): 20.yukseklik = 1 + max(0, 0) = 1 (önceden 3). Önce alttaki düğüm güncellenir; y'nin yüksekliği buna bağlı.

20h=1x10h=040h=2y30h=050h=160h=0

guncelle(y): 40.yukseklik = 1 + max(1, 1) = 2 (değişmedi, ama yeniden hesaplanmalı).

400h=2y200h=1x100h=0300h=050−1h=1600h=0

return y: 40 bu alt ağacın yeni kökü (aynı bağlar, yeniden çizildi). Tüm bf ∈ {−1, 0, 1}; kök ortada sıra değişmedi. Metotta 2 bağ değişti, 3. bağı (ebeveynden) çağıran atar → O(1).

Denge ve AVL · Kodu adım adım çalıştır

Kod İzleme: dengele(50) — LR durumu

AvlDugum dengele(AvlDugum d) {
    guncelle(d);
    if (bf(d) > 1) {       // sol ağır
        if (bf(d.sol) < 0)  // LR
            d.sol = solaDondur(d.sol);
        return sagaDondur(d); // LL
    }
    if (bf(d) < -1) {      // sağ ağır
        if (bf(d.sag) > 0)  // RL
            d.sag = sagaDondur(d.sag);
        return solaDondur(d); // RR
    }
    return d;
}
501h=220−1h=1100h=0301h=1d250h=0600h=0

25 eklendikten sonra özyineleme geri dönerken dengele(30) çağrılır. guncelle: yükseklik 0 → 1, bf = 1.

501h=220−1h=1100h=0301h=1d250h=0600h=0

bf = 1: ne > 1 ne < −1 → döndürme yok, return d. Bir üst çağrıya dönülür.

502h=220−1h=2d100h=0301h=1250h=0600h=0

25 eklendikten sonra özyineleme geri dönerken dengele(20) çağrılır. guncelle: yükseklik 1 → 2, bf = −1.

502h=220−1h=2d100h=0301h=1250h=0600h=0

bf = −1: ne > 1 ne < −1 → döndürme yok, return d. Bir üst çağrıya dönülür.

502h=3d20−1h=2100h=0301h=1250h=0600h=0

dengele(50): guncelle → yükseklik 3, bf = h(20) − h(60) = 2 − 0 = 2.

502h=3d20−1h=2100h=0301h=1250h=0600h=0

bf(d) = 2 > 1 → sol ağır. Hangi döndürme? Sol çocuğun eğimine bakılır.

502h=3d20−1h=2100h=0301h=1250h=0600h=0

bf(d.sol) = bf(20) = -1 < 0: sol çocuk sağa eğik → LR (üçgen). Tek sağa döndürme yetmez.

502h=3d302h=2200h=1100h=0250h=0600h=0

d.sol = solaDondur(20): 30 yukarı çıkar, 25 → 20'nin sağına geçer. Üçgen düz çizgiye (LL) döndü; bf(50) hâlâ 2.

300h=2200h=1100h=0250h=050−1h=1600h=0

return sagaDondur(50): 30 kök olur, 50 onun sağına iner; 30'un eski sağı (null) 50'nin soluna geçer.

300h=2200h=1100h=0250h=050−1h=1600h=0

dengele(50) yeni alt ağaç kökü 30'u döndürür; ekle onu çağırana (burada kok) atar. Yükseklik 3'ten 2'ye indi.

Denge ve AVL · Kodu adım adım çalıştır

AVL Ekleme İzleme: 10, 20, 30

100

Ağaç boş: ekle(null, 10) → d == null, yeni düğüm döner; kök = 10, bf = 0.

null100d

ekle(kok, 20) BST gibi iner: 20 > 10 → sağ.

10−1200

d == null → yeni yaprak 20 döner ve 10'un sağ bağına atanır. Dönüşte yol üzerindeki bf'ler değişti.

10−1200

Dönüşte her atada return dengele(d): bf(10) = −1 → hepsinde |bf| ≤ 1, döndürme yok.

null10−1200d

ekle(kok, 30) BST gibi iner: 30 > 10 → sağ; 30 > 20 → sağ.

10−220−1300

d == null → yeni yaprak 30 döner ve 20'nin sağ bağına atanır. Dönüşte yol üzerindeki bf'ler değişti.

10−2d20−1300

dengele(10): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 20'de bf = −1 → RR durumu.

200100300

10'da solaDondur: 20 yukarı çıkar, 10 onun sol çocuğu olur. Tüm bf ∈ {−1, 0, 1}.

AvlDugum ekle(AvlDugum d, int k) {
    if (d == null) return new AvlDugum(k);
    if (k < d.anahtar)
        d.sol = ekle(d.sol, k);
    else if (k > d.anahtar)
        d.sag = ekle(d.sag, k);
    else return d;          // tekrar yok
    return dengele(d);
}

Denge ve AVL · Kodu adım adım çalıştır

AVL Ekleme İzleme (devam): 40, 50, 25

null200100300d

ekle(kok, 40) BST gibi iner: 40 > 20 → sağ; 40 > 30 → sağ.

20−110030−1400

d == null → yeni yaprak 40 döner ve 30'un sağ bağına atanır. Dönüşte yol üzerindeki bf'ler değişti.

20−110030−1400

Dönüşte her atada return dengele(d): bf(30) = −1, bf(20) = −1 → hepsinde |bf| ≤ 1, döndürme yok.

null20−110030−1400d

ekle(kok, 50) BST gibi iner: 50 > 20 → sağ; 50 > 30 → sağ; 50 > 40 → sağ.

20−210030−240−1500

d == null → yeni yaprak 50 döner ve 40'ın sağ bağına atanır. Dönüşte yol üzerindeki bf'ler değişti.

20−210030−2d40−1500

dengele(30): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 40'ta bf = −1 → RR durumu.

20−1100400300500

30'da solaDondur: 40 yukarı çıkar, 30 onun sol çocuğu olur. Tüm bf ∈ {−1, 0, 1}.

null20−1100400300d500

ekle(kok, 25) BST gibi iner: 25 > 20 → sağ; 25 < 40 → sol; 25 < 30 → sol.

20−2100401301250500

d == null → yeni yaprak 25 döner ve 30'un sol bağına atanır. Dönüşte yol üzerindeki bf'ler değişti.

20−2d100401301250500

dengele(20): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 40'ta bf = 1 → RL durumu.

20−2d10030−125040−1500

1. döndürme: 40'ta sagaDondur → üçgen düz çizgiye (sağ-sağ) döner. bf(20) hâlâ −2.

30020010025040−1500

2. döndürme: 20'de solaDondur: 30 yukarı çıkar; 25, 20'nin sağına geçer. Ağaç yeniden AVL. Tüm bf ∈ {−1, 0, 1}.

AvlDugum ekle(AvlDugum d, int k) {
    if (d == null) return new AvlDugum(k);
    if (k < d.anahtar)
        d.sol = ekle(d.sol, k);
    else if (k > d.anahtar)
        d.sag = ekle(d.sag, k);
    else return d;          // tekrar yok
    return dengele(d);
}

Kırmızı-Siyah Ağaçlar

Kırmızı-Siyah Ağaç (Red-Black Tree)

  1. Her düğüm kırmızı ya da siyahtır.
  2. Kök siyahtır.
  3. Tüm yapraklar (NIL, boş çocuklar) siyah kabul edilir.
  4. Kırmızı düğümün çocukları siyahtır: ardışık iki kırmızı olmaz.
  5. Bir düğümden NIL yapraklara giden her yolda aynı sayıda siyah düğüm vardır (siyah yükseklik).
13 8 1 6 11 17 15 25 22 27

Örnek: kökten her NIL'e giden yolda 2 siyah düğüm var (kök dahil, NIL hariç)

Kırmızı-Siyah Ağaçlar

Kurallar Neden Dengeyi Garanti Eder?

1052015302540en kısa: 2en uzun: 4 = 2·2
  • En kısa yol: yalnız siyah düğümlerden oluşabilir → b düğüm (şekilde 10–5: b = 2).
  • En uzun yol: kırmızılar ardışık olamaz; en fazla her siyahın altına bir kırmızı girer → en çok 2b düğüm (10–20–30–40).
  • Sonuç: en uzun yol ≤ 2 × en kısa yol; buradan h ≤ 2·log2(n + 1).
  • Maliyet: arama, ekleme, silme O(log n); eklemede en fazla 2, silmede en fazla 3 döndürme, gerisi renk değiştirme.
Kırmızı-siyah ağaç, AVL'ye göre daha gevşek dengelidir (biraz daha yüksek olabilir) ama güncellemeleri daha ucuzdur. Java TreeMap/TreeSet ve C++ std::map bu yapıyı kullanır.

Kırmızı-Siyah Ağaçlar

Kırmızı-Siyah Ağaca Ekleme

Yeni düğüm BST gibi eklenir ve kırmızı boyanır (siyah yükseklik bozulmasın diye). Ebeveyn de kırmızıysa kural 4 bozulur; amcaya (ebeveynin kardeşi) bakılır:

Amca kırmızıRenk değiştir: ebeveyn ve amca siyah, dede kırmızı. Sorun dedeye taşınabilir → oradan devam et.
Amca siyah, üçgenDüğüm-ebeveyn-dede zikzak yapıyorsa ebeveynde döndür → çizgi durumuna dönüşür.
Amca siyah, çizgiDedede döndür; eski ebeveyn siyah, eski dede kırmızı olur. Bitti.
  • Ebeveyn siyahsa hiçbir şey yapılmaz. En sonda kök her zaman siyaha boyanır.
  • NIL amca siyah sayılır.
  • Silme benzer ama daha çok durumludur ("çift siyah" düzeltmesi).
g p x udedeebeveynamcayeni düğüm

Kırmızı-Siyah Ağaçlar

Ekleme Sonrası Düzeltme Kodu

void duzelt(Dugum x) {      // x: yeni, kırmızı
    while (x != kok && kirmizi(x.ebeveyn)) {
        Dugum p = x.ebeveyn, g = p.ebeveyn;
        Dugum u = (p == g.sol) ? g.sag : g.sol;
        if (kirmizi(u)) {         // amca kırmızı
            p.renk = u.renk = SIYAH;
            g.renk = KIRMIZI;
            x = g;                // sorun dedeye
        } else {                  // amca siyah
            if (ucgen(x, p, g)) { // zikzak
                dondur(p, x);     // çizgiye çevir
                Dugum t = x; x = p; p = t;
            }
            dondur(g, p);         // çizgi
            p.renk = SIYAH; g.renk = KIRMIZI;
        }
    }
    kok.renk = SIYAH;
}
// Ekleme: BST gibi yaprağa ekle,
// ebeveyni bağla, kırmızı boya:
//   x = bstEkle(anahtar);
//   x.renk = KIRMIZI; duzelt(x);

// NIL (null) siyah sayılır
boolean kirmizi(Dugum n) {
    return n != null
        && n.renk == KIRMIZI;
}
// x–p–g zikzak (üçgen) mı?
boolean ucgen(Dugum x, Dugum p,
              Dugum g) {
    return (p == g.sol)
        != (x == p.sol);
}
// dondur(a, b): b, a'nın çocuğu;
// b'yi a'nın yerine çıkarır (b
// soldaysa sağa, sağdaysa sola
// döndürme; ebeveyn bağları dahil)

Kırmızı-Siyah Ağaçlar · Kodu adım adım çalıştır

Kod İzleme: duzelt — 10, 20, 30, 15 eklenir

void duzelt(Dugum x) {      // x: yeni, kırmızı
    while (x != kok && kirmizi(x.ebeveyn)) {
        Dugum p = x.ebeveyn, g = p.ebeveyn;
        Dugum u = (p == g.sol) ? g.sag : g.sol;
        if (kirmizi(u)) {         // amca kırmızı
            p.renk = u.renk = SIYAH;
            g.renk = KIRMIZI;
            x = g;                // sorun dedeye
        } else {                  // amca siyah
            if (ucgen(x, p, g)) { // zikzak
                dondur(p, x);     // çizgiye çevir
                Dugum t = x; x = p; p = t;
            }
            dondur(g, p);         // çizgi
            p.renk = SIYAH; g.renk = KIRMIZI;
        }
    }
    kok.renk = SIYAH;
}
10x

10 BST gibi eklendi (kök) ve kırmızı boyandı; duzelt(x) çağrılır.

10x

x kökün kendisi → döngü koşulu yanlış, döngüye girilmez.

10x

kok.renk = SIYAH: kök her zaman siyah (kural 2).

1020x

20 BST gibi eklendi (10'un sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.

1020x

x.ebeveyn (10) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.

102030x

30 BST gibi eklendi (20'nin sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.

102030x

x.ebeveyn (20) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.

NILu10g20p30x

p = ebeveyn (20), g = dede (10), u = amca = NIL (boş, siyah sayılır).

NILu10g20p30x

Amca siyah → döndürme gerekir. x–p–g düz çizgi: ucgen() yanlış, doğrudan satır 14.

20p10g30x

dondur(g, p): dede 10'da sola döndürme → p (20) yukarı çıkar, g onun çocuğu olur.

20p10g30x

Renkler: p siyah, g kırmızı. Ardışık kırmızı kalmadı, siyah yükseklik korundu.

20p10g30x

x.ebeveyn (20) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.

201015x30

15 BST gibi eklendi (10'un sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.

201015x30

x.ebeveyn (10) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.

20g10p15x30u

p = ebeveyn (10), g = dede (20), u = amca = 30.

20g10p15x30u

Amca kırmızı → yalnızca renk değiştir: p ve u siyah, g kırmızı. Her yolda siyah sayısı aynı kalır.

20x101530

x = g (20): g kırmızı oldu; onun ebeveyni de kırmızı olabilir → döngü dededen devam eder.

20x101530

x kökün kendisi → döngü koşulu yanlış, döngüye girilmez.

20x101530

kok.renk = SIYAH: kök her zaman siyah (kural 2). Siyah yükseklik 1 arttı.

Kırmızı-Siyah Ağaçlar · Kodu adım adım çalıştır

Kod İzleme: duzelt — 12 ve 5 eklenir

void duzelt(Dugum x) {      // x: yeni, kırmızı
    while (x != kok && kirmizi(x.ebeveyn)) {
        Dugum p = x.ebeveyn, g = p.ebeveyn;
        Dugum u = (p == g.sol) ? g.sag : g.sol;
        if (kirmizi(u)) {         // amca kırmızı
            p.renk = u.renk = SIYAH;
            g.renk = KIRMIZI;
            x = g;                // sorun dedeye
        } else {                  // amca siyah
            if (ucgen(x, p, g)) { // zikzak
                dondur(p, x);     // çizgiye çevir
                Dugum t = x; x = p; p = t;
            }
            dondur(g, p);         // çizgi
            p.renk = SIYAH; g.renk = KIRMIZI;
        }
    }
    kok.renk = SIYAH;
}
20101512x30

12 BST gibi eklendi (15'in sol çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.

20101512x30

x.ebeveyn (15) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.

NILu2010g15p12x30

p = ebeveyn (15), g = dede (10), u = amca = NIL (boş, siyah sayılır).

NILu2010g15p12x30

Amca siyah → döndürme gerekir. x–p–g üçgen (zikzak): x ile p zıt yönde çocuk.

NILu2010g12x15p30

dondur(p, x): 15'te sağa döndürme → x yukarı, p aşağı. Üçgen düz çizgiye döndü (hâlâ ardışık kırmızı).

NILu2010g12p15x30

x ile p rolleri değişir: artık x = 15 (alttaki), p = 12.

2012p10g15x30

dondur(g, p): dede 10'da sola döndürme → p (12) yukarı çıkar, g onun çocuğu olur.

2012p10g15x30

Renkler: p siyah, g kırmızı. Ardışık kırmızı kalmadı, siyah yükseklik korundu.

2012p10g15x30

x.ebeveyn (12) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.

2012105x1530

5 BST gibi eklendi (10'un sol çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.

2012105x1530

x.ebeveyn (10) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.

2012g10p5x15u30

p = ebeveyn (10), g = dede (12), u = amca = 15.

2012g10p5x15u30

Amca kırmızı → yalnızca renk değiştir: p ve u siyah, g kırmızı. Her yolda siyah sayısı aynı kalır.

2012x1051530

x = g (12): g kırmızı oldu; onun ebeveyni de kırmızı olabilir → döngü dededen devam eder.

2012x1051530

x.ebeveyn (20) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.

Dengeli Arama Ağaçları

AVL ve Kırmızı-Siyah Ağaçlarda Silme

Ortak başlangıçÖnce normal BST silmesi uygulanır. İki çocuklu düğüm, ardılı veya öncülüyle değiştirilir; gerçek silme yaprak ya da tek çocuk durumuna iner.
Neden ek işlem gerekir?Düğüm kaldırmak AVL yükseklik farkını veya kırmızı-siyah ağacın siyah yükseklik kuralını bozabilir.
AVL düzeltmesiSilinen düğümden köke doğru yükseklikleri güncelle. |bf| = 2 olan her düğümde LL, RR, LR veya RL döndürmesi uygula. Birden fazla atada düzeltme gerekebilir.
Kırmızı-siyah düzeltmesiKırmızı düğümün silinmesi kolaydır. Siyah düğüm eksilirse kardeşin rengine ve çocuklarına göre yeniden renklendirme/döndürme yap; eksik siyahlık köke taşınabilir.
YapıYukarı doğru incelemeDöndürmeToplam
AVLO(log n)O(log n) olabilirO(log n)
Kırmızı-siyahO(log n)en fazla 3O(log n)

Ekleme ve silme BST sırasını korur; dengeleme yalnızca ağacın biçimini ve yardımcı bilgilerini değiştirir.

Kırmızı-Siyah Ağaçlar

AVL mi, Kırmızı-Siyah mı?

ÖzellikAVLKırmızı-Siyah
Denge ölçütü|bf| ≤ 1 her düğümderenk kuralları, eşit siyah yükseklik
En büyük yükseklik≈ 1,44 log2 n2 log2(n + 1)
AramaO(log n), biraz daha hızlı (daha kısa)O(log n)
Ekleme döndürmesien fazla 2 (bir tek veya bir çift)en fazla 2
Silme döndürmesiO(log n) olabiliren fazla 3
Düğümde ek bilgiyükseklik (veya bf)1 bit renk
Terciharamanın baskın olduğu işlersık ekleme/silme, kütüphaneler

Splay Ağaçları

Splay Ağacı (Splay Tree)

  • Sleator ve Tarjan (1985): kendini düzenleyen (self-adjusting) BST.
  • Her erişimde (arama, ekleme, silme) ilgili düğüm döndürmelerle köke taşınır: splay işlemi.
  • Düğümlerde denge bilgisi (yükseklik, renk) tutulmaz.
  • Tek bir işlem O(n) sürebilir; ama m işlemlik her dizi O(m log n) sürer: amortize O(log n).
YerellikSık erişilen anahtarlar köke yakın durur; önbellek gibi davranır.
BasitlikEk alan yok, tek bir splay yordamı tüm işlemlere yeter.
DezavantajArama bile ağacı değiştirir; tek işlem için garanti yok.

Splay Ağaçları

Splay Adımları: Zig, Zig-Zig, Zig-Zag

x: splay edilen düğüm, p: ebeveyni, g: dedesi. x kök olana kadar bu adımlar tekrarlanır.

20 10 5 15 30 x = 10, ebeveyni p = 20 kök sağa döndür(p) 10 5 20 15 30 x kök oldu; 15, p'nin soluna geçti

Zig: x'in ebeveyni p köktür → p'de tek döndürme. (Yalnızca son adımda olabilir.)

30 20 10 x, p, g aynı yönde (sol-sol) döndür(g) 20 10 30 önce dede g = 30 döndür(p) 10 20 30 sonra ebeveyn p = 20

Zig-zig: x ve p aynı yönde çocuk → önce g'de, sonra p'de döndür. (AVL'deki tek döndürmeden farklı!)

30 10 20 x sağ, p sol çocuk (zikzak) sola döndür(p) 30 20 10 önce ebeveyn p = 10 sağa döndür(g) 20 10 30 sonra dede g = 30

Zig-zag: x ve p zıt yönde → önce p'de, sonra g'de döndür (AVL çift döndürmesiyle aynı).

Splay Ağaçları · Kodu adım adım çalıştır

Kod İzleme: splay(50), sonra splay(10)

void splay(Dugum x) {
    while (x != kok) {
        Dugum p = x.ebeveyn, g = p.ebeveyn;
        if (g == null)                // zig
            dondur(p, x);
        else if (ayniYon(x, p, g)) {  // zig-zig
            dondur(g, p);   // önce dede
            dondur(p, x);
        } else {                      // zig-zag
            dondur(p, x);   // önce ebeveyn
            dondur(g, x);
        }
    }
}
80402010306050x7090

splay(50): önce normal BST araması 80 → 40 → 60 → 50; bulunan düğüm x olur. (ayniYon: x ve p aynı yönde çocuk mu; dondur: kırmızı-siyah kodundakiyle aynı tek döndürme.)

8040g20103060p50x7090

x kök değil. p = 60, g = 40.

8040g20103050x60p7090

x ve p zıt yönde → zig-zag. Önce ebeveyn: 60'ta sağa döndür.

8050x40g20103060p7090

Sonra dede: 40'ta sola döndür → x, p ile g'nin ebeveyni olur.

80p50x40201030607090

x kök değil. p = 80, g = null (p kök).

50x4020103080p607090

g == null → zig: kök 80'de sağa döndürme; x kök olur.

50x4020103080607090

x == kok → döngü biter. 50 artık kök; sık erişilen anahtarlar köke yakın durur.

50402010x3080607090

splay(10): önce normal BST araması 50 → 40 → 20 → 10; bulunan düğüm x olur.

5040g20p10x3080607090

x kök değil. p = 20, g = 40.

5020p10x40g3080607090

x ve p aynı yönde → zig-zig. Önce dede: 40'ta sağa döndür (p yukarı).

5010x20p40g3080607090

Sonra ebeveyn: 20'de sağa döndür → x iki düzey yükseldi.

50p10x20403080607090

x kök değil. p = 50, g = null (p kök).

10x50p20403080607090

g == null → zig: kök 50'de sağa döndürme; x kök olur.

10x5020403080607090

x == kok → döngü biter. 10 kök; az önce erişilen 50 hâlâ köke yakın (derinlik 1). Tek işlem O(n) olabilir, amortize O(log n).

Çok Yollu Arama Ağaçları

B-Ağacı (B-Tree)

  • Bir düğümde birden çok anahtar ve çok sayıda çocuk bulunan, dengeli bir çok yollu arama ağacıdır.
  • Derecesi m olan B-ağacında bir düğüm en fazla m çocuk, m − 1 anahtar tutar; kök dışındaki iç düğümlerde en az ⌈m/2⌉ çocuk bulunur.
  • Düğüm içindeki anahtarlar sıralıdır; tüm yapraklar aynı derinliktedir.
  • Yükseklik O(log n) kalır; ekleme/silmede düğümler bölünür veya birleştirilir.
2040510253035506030 aranıyor: 20 < 30 < 40 → orta çocuk
Neden? Bir düğüm bir disk bloğuna denk getirilir. Geniş ve alçak ağaç = az disk erişimi. Veritabanı ve dosya sistemi indislerinin temelidir.

Derecesi 4 olan B-ağacı (2-3-4 ağacı) kırmızı-siyah ağacın eşdeğeridir: her siyah düğüm kırmızı çocuklarıyla tek düğümde toplanınca 2-3-4 ağacı elde edilir. Adım adım ekleme ve bölünme: B-Ağacı simülatörü.

Çok Yollu Arama Ağaçları

B-Ağacında Arama ve Ekleme

51025303550602040kök: 20 < 30 < 40 → orta çocuğa in

1 · Düğüm içinde ara. 30 aranıyor: kökteki anahtarlar sıralı karşılaştırılır; 20 < 30 < 40 → bu aralığa karşılık gelen orta çocuğa inilir.

51025303550602040yaprak: 25, 30 → bulundu (2 düğüm okundu)

Yaprakta 30 bulunur. Her düzeyde yalnızca bir düğüm (bir disk bloğu) okunur → maliyet yükseklikle orantılı: O(logm n).

51025303335taşma!50602040m = 4: bir düğümde en çok 3 anahtar

2 · Yaprağa ekle. 33 için aynı yol izlenir ve 33 yaprakta sıralı yerine konur. Artık 4 anahtar var: kapasite (m − 1 = 3) aşıldı.

510253035506020334025 30 | 33 ↑ | 35

3 · Taşmayı böl. Ortadaki anahtar (33) ebeveyne çıkar, kalanlar iki düğüme ayrılır (25 30 ve 35). Ebeveyn de taşarsa bölünme köke kadar yayılır; kök bölünürse yeni kök oluşur ve yükseklik bir artar.

Kök bölünürseYeni bir kök oluşur ve ağacın yüksekliği yalnızca bir artar. Tüm yapraklar yine aynı derinlikte kalır.
SilmeEksik dolan düğüm kardeşten anahtar ödünç alır veya kardeşle birleşir; denge ve doluluk sınırları korunur.

Çok Yollu Arama Ağaçları · Adım adım

B-Ağacına Ekleme ve Bölünme (m = 3)

Sırayla eklenen anahtarlar: 10, 20, 30, 40, 50, 60, 70 · derece m = 3 → düğüm başına en çok 2 anahtar, 3 çocuk.

10

10 eklenir. kök yaprağa sıralı yerine konur; düğümde 1 anahtar var (≤ 2), bölünme gerekmez.

1020

20 eklenir. kök yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.

102030taşma!

30 eklenir → düğüm [10, 20, 30] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.

103020

Kök bölündü: ortanca 20 yeni köke çıkar; 10 ve 30 iki ayrı düğüm olur. Yükseklik bir arttı, tüm yapraklar yine aynı derinlikte.

10304020

40 eklenir. Kökten sağa inilir; yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.

10304050taşma!20

50 eklenir → düğüm [30, 40, 50] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.

1030502040

Ortanca 40 ebeveyne çıkar; kalanlar 30 ve 50 olarak iki düğüme ayrılır. Ebeveynde yer var → bölünme burada durur.

103050602040

60 eklenir. Kökten sağa inilir; yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.

1030506070taşma!2040

70 eklenir → düğüm [50, 60, 70] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.

10305070204060taşma!

Ortanca 60 ebeveyne çıktı, düğüm 50 | 70 olarak ikiye ayrıldı. Ama ebeveyn [20, 40, 60] şimdi 3 anahtarlı → bölünme yukarı yayılır.

10305070206040

Kök bölündü: ortanca 40 yeni köke çıkar; 20 ve 60 iki ayrı düğüm olur. Yükseklik bir arttı, tüm yapraklar yine aynı derinlikte.

10305070206040

Sonuç: 7 anahtar, 3 düzey; her iç düğümün 2 çocuğu var, tüm yapraklar aynı derinlikte. B-ağacı aşağıdan yukarı büyür: yükseklik yalnızca kök bölününce artar.

Çok Yollu Arama Ağaçları

B+ Ağacı (B+ Tree)

2040510202530405060yapraklar bağlı liste: aralık sorgusu soldan sağa okunur
Veri yapraklardaTüm anahtarlar (ve kayıtlar) yapraklarda bulunur; iç düğümler yalnızca yönlendirme anahtarı tutar.
Bağlı yapraklarYapraklar birbirine bağlıdır; "20 ile 50 arası" gibi aralık sorguları hızlıdır.
Daha geniş iç düğümlerİç düğümler kayıt taşımadığından bir bloğa daha çok anahtar sığar → ağaç daha alçak.

Çoğu ilişkisel veritabanı indisi B+ ağacıdır.

Çok Yollu Arama Ağaçları · Adım adım

B+ Ağacında Aralık Sorgusu: 10 ile 45 arası

2040510202530405060sorgu: 10 ≤ anahtar ≤ 45sonuç: —

Kökten başla: 10 < 20 → alt sınır 10'u içeren yaprak en soldaki çocuktadır. (Arama gibi: O(log n) düzey.)

2040510202530405060sorgu: 10 ≤ anahtar ≤ 45sonuç: 10

İlk yaprak [5 10]: 5 < 10 atlanır, 10 alınır. Yaprağın sonuna gelindi → sonraki yaprak bağına geç (yukarı çıkmadan).

2040510202530405060sorgu: 10 ≤ anahtar ≤ 45sonuç: 10 20 25 30

İkinci yaprak: 20, 25, 30 hepsi aralıkta → alınır. Yine bağı izle.

2040510202530405060sorgu: 10 ≤ anahtar ≤ 45sonuç: 10 20 25 30 40

Üçüncü yaprak: 40 alınır; 50 > 45 → dur. Sonuç: 10 20 25 30 40.

2040510202530405060sorgu: 10 ≤ anahtar ≤ 45sonuç: 10 20 25 30 40

Maliyet: bir kez aşağı inmek O(log n) + okunan yapraklar. B-ağacında aynı sorgu iç düğümlere tekrar tekrar inip çıkmayı gerektirirdi; B+ ağacında yaprak zinciri O(log n + k) sağlar (k: sonuç sayısı).

Özet

Özet: Arama Ağaçlarında İşlem Maliyetleri

YapıYükseklikAramaEklemeSilmeNasıl dengelenir?
BSTlog n … nO(h)O(h)O(h)dengelenmez; en kötü O(n)
AVL≤ 1,44 log nO(log n)O(log n)O(log n)bf ∈ {−1, 0, 1}; LL, RR, LR, RL döndürmeleri
Kırmızı-Siyah≤ 2 log(n+1)O(log n)O(log n)O(log n)renk değiştirme + en çok 2–3 döndürme
SplaysınırsızO(log n)*O(log n)*O(log n)*erişilen düğüm zig / zig-zig / zig-zag ile köke
B-ağacı / B+≈ logm nO(log n)O(log n)O(log n)taşan düğüm bölünür; tüm yapraklar aynı derinlikte

* Amortize; tek işlem O(n) olabilir. Tüm yapılarda kök ortada gezinme O(n) ve anahtarları sıralı verir; min/max O(h).

BST özelliğisol < kök < sağ, tüm alt ağaç için.
Silmeyaprak → kaldır; tek çocuk → atla; iki çocuk → ardıl/öncül.
DöndürmeO(1), kök ortada sırayı korur, yükseklikleri değiştirir.

Özet

Etkileşimli Simülatörler

İkili Arama Ağacı SimülatörüArama, ekleme, üç silme durumu ve gezinmeler.
İkili Arama Ağacı Gezinme SimülatörüEkleme, arama, silme; kök önde / ortada / sonda ve düzey sırası gezinmeleri.
AVL Ağacı SimülatörüDenge faktörleri ve LL / RR / LR / RL döndürmeleri.
Kırmızı-Siyah Ağaç SimülatörüRenk değiştirme ve döndürmelerle ekleme.
Splay Ağacı SimülatörüErişilen düğümün zig, zig-zig, zig-zag ile köke taşınması.
B-Ağacı SimülatörüAnahtar ekledikçe düğümlerin bölünmesi ve ağacın dengeli büyümesi.
B+ Ağacı SimülatörüVerinin yapraklarda tutulduğu, yaprakları bağlı çok yollu ağaç.

Özet

Sık Yapılan Hatalar

Dönüş değerini atamamaksil(kok, 40); yazmak kök silindiğinde ağacı bozar. Doğrusu: kok = sil(kok, 40); (ekle için de aynı).
Yalnız çocuklara bakmakBST özelliği tüm alt ağaç içindir; doğrulamada izinli (min, max) aralığı aşağı taşınmalı (bstMi).
Nesneleri == ile karşılaştırmakReferansı karşılaştırır. Anahtarlar compareTo ile, eşitlik equals ile sınanır.
Yükseklik güncelleme sırasıAVL döndürmesinden sonra önce alta inen düğüm, sonra yeni kök güncellenir; tersi yanlış bf verir.
"BST her zaman O(log n)"Yanlış: sıralı girdiyle h = n − 1 olur. O(log n) ancak dengeli (AVL, kırmızı-siyah) ağaçlarda garantidir.
null çocuğu unutmakBoş alt ağacın yüksekliği −1'dir, kırmızı-siyahta NIL siyah sayılır; h(null), kirmizi(null) gibi yardımcılar bu yüzden var.

Özet

Örnek Problemler

  1. Boş BST'ye 50, 30, 70, 20, 40, 60, 80 sırayla eklenir. Yükseklik nedir? Kök ortada gezinme çıktısı nedir?
  2. Aynı anahtarlar 20, 30, 40, 50, 60, 70, 80 sırasıyla eklenirse yükseklik ve arama maliyeti ne olur?
  3. 1. sorudaki ağaçtan kök 50, kök ortada ardıl kullanılarak silinirse ağaç nasıl olur?
  4. Boş AVL ağacına 3, 2, 1 eklenir. Hangi durum oluşur, hangi döndürme yapılır?
  5. Boş AVL ağacına 10, 30, 20 eklenir. Hangi durum oluşur, sonuç ağaç nedir?
  6. Kırmızı-siyah ağaçta kökten NIL'e giden en uzun yol, en kısa yolun en fazla kaç katıdır? Neden?
  7. Splay ağacında zig-zig adımında döndürmeler hangi sırayla yapılır?
Cevapları göster
  1. h = 2 (tam dengeli); kök ortada: 20 30 40 50 60 70 80.
  2. Sağa doğru zincir: h = 6; arama O(n) (en kötü 7 karşılaştırma).
  3. Ardıl 60 köke kopyalanır, 70'in solundaki 60 silinir: 60(30(20, 40), 70(—, 80)).
  4. 3'te bf = 2, sol çocuk 2'de bf = 1 → LL; 3'te sağa döndürme → 2(1, 3).
  5. 10'da bf = −2, sağ çocuk 30'da bf = +1 → RL; 30'da sağa, 10'da sola → 20(10, 30).
  6. 2 katı: siyah sayısı her yolda eşit ve kırmızılar ardışık olamaz.
  7. Önce dede (g), sonra ebeveyn (p) döndürülür.

Özet

Alıştırmalar

  1. taban(kok, x): ≤ x olan en büyük anahtarı (yoksa null) döndüren metodu yazın.
  2. BST'de k. en küçük anahtarı bulan bir metot yazın. Zaman karmaşıklığı nedir?
  3. 1, 2, …, 7 boş bir BST'ye hangi sırayla eklenirse yüksekliği 2 olan mükemmel ağaç oluşur?
  4. Boş bir AVL ağacına 1, 2, …, 7 sırayla eklenirse son ağaç nasıl olur? Kaç döndürme yapılır?
  5. Yüksekliği 5 olan bir AVL ağacı en az kaç düğüm içerir?
  6. aralik(kok, lo, hi) neden O(n) değil de O(h + s) sürer?
Cevaplar / ipuçları
  1. ardil'in simetriği: x >= kok.veri ise aday = kok; kok = kok.sag; değilse sola. O(h).
  2. Sayaçlı kök ortada gezinme: k. ziyaret edilen düğüm cevaptır → O(h + k).
  3. Önce orta, sonra yarıların ortası: ör. 4, 2, 6, 1, 3, 5, 7.
  4. 4(2(1, 3), 6(5, 7)); 3, 5, 6 ve 7 eklenirken birer sola döndürme → 4 döndürme.
  5. N(5) = 1 + N(4) + N(3) = 1 + 12 + 7 = 20.
  6. lo'dan küçük düğümün solu, hi'den büyük düğümün sağı hiç ziyaret edilmez (budama).

Özet

Kaynaklar

  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
  • Sedgewick ve Wayne, Algorithms, 4. baskı, 2011.

Son

Bölüm 10: Arama Ağaçları
Sonraki sunum: Bölüm 11: Öncelikli Kuyruklar ve Heap · Çalışma soruları · Simülatörler: İkili Arama Ağacı Simülatörü, İkili Arama Ağacı Gezinme Simülatörü, Kırmızı-Siyah Ağaç Simülatörü, AVL Ağacı Simülatörü, Splay Ağacı Simülatörü, B-Ağacı Simülatörü, B+ Ağacı Simülatörü · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.