Bölüm 10: Arama Ağaçları
Giriş
Öğrenme Çıktıları
Arama, ekleme ve silme işlemlerini ikili arama ağacında uygulamak.
Ağaç yüksekliğinin işlem maliyetlerine etkisini açıklamak.
AVL döndürmelerini ve kırmızı-siyah ağaç kurallarını yorumlamak.
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.
Arama Ağaçları
Temel İşlemler
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
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.
- Ö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
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
}
minDeger(kok): dugum kökten (6) başlar. Ön koşul: ağaç boş değil.
dugum.solCocuk = 2 ≠ null → döngü devam.
dugum = dugum.solCocuk → 2. Sola iniyoruz.
dugum.solCocuk = 1 ≠ null → döngü devam.
dugum = dugum.solCocuk → 1. Sola iniyoruz.
dugum.solCocuk == null → döngü biter. 1'in sol çocuğu yok.
return 1: ağacın en küçük anahtarı. (3 daha derinde ama 4'ün solunda; en küçük hep en soldadır.)
maxDeger(kok): dugum kökten (6) başlar. Ön koşul: ağaç boş değil.
dugum.sagCocuk = 8 ≠ null → döngü devam.
dugum = dugum.sagCocuk → 8. Sağa iniyoruz.
dugum.sagCocuk == null → döngü biter. 8'in sağ çocuğu yok.
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)
inorder(6): d ≠ null. Önce sol alt ağaç: inorder(2) çağrılır; 6 bekler.
inorder(2): d ≠ null. Önce sol alt ağaç: inorder(1) çağrılır; 2 bekler.
inorder(1): sol çocuk yok → inorder(null) satır 2'de hemen döner.
Sol taraf bitti → 1 yazdırılır. (Sol alt ağaçtaki her anahtar 1'den küçüktü.)
Sağ çocuk yok → çerçeve kapanır; çağıran inorder(2) kaldığı yerden devam eder.
Sol taraf bitti → 2 yazdırılır. (Sol alt ağaçtaki her anahtar 2'den küçüktü.)
Şimdi sağ alt ağaç: inorder(4).
inorder(4): d ≠ null. Önce sol alt ağaç: inorder(3) çağrılır; 4 bekler.
inorder(3): sol çocuk yok → inorder(null) satır 2'de hemen döner.
Sol taraf bitti → 3 yazdırılır. (Sol alt ağaçtaki her anahtar 3'ten küçüktü.)
Sağ çocuk yok → çerçeve kapanır; çağıran inorder(4) kaldığı yerden devam eder.
Sol taraf bitti → 4 yazdırılır. (Sol alt ağaçtaki her anahtar 4'ten küçüktü.)
Sağ çocuk yok → çerçeve kapanır; çağıran inorder(2) kaldığı yerden devam eder.
2'nin sağ alt ağacı da bitti → inorder(2) kapanır; çağıran kaldığı yerden devam eder.
Sol taraf bitti → 6 yazdırılır. (Sol alt ağaçtaki her anahtar 6'dan küçüktü.)
Şimdi sağ alt ağaç: inorder(8).
inorder(8): sol çocuk yok → inorder(null) satır 2'de hemen döner.
Sol taraf bitti → 8 yazdırılır. (Sol alt ağaçtaki her anahtar 8'den küçüktü.)
Sağ çocuk yok → çerçeve kapanır; çağıran inorder(6) kaldığı yerden devam eder.
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ı
class AgacDugumu { int veri; AgacDugumu sol, sag; AgacDugumu(int veri) { this.veri = veri; this.sol = null; this.sag = null; } }
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.
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); }
== 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); }
ara(kok, 5) çağrıldı; kok kökü (6) gösterir. kok ≠ null. k = compareTo = -1 çünkü 5 < 6.
k < 0 → sol alt ağaçta ara: ara(kok.solCocuk, 5) yeni bir çerçeve açar (kok = 4).
kok ≠ null. k = compareTo = 1 çünkü 5 > 4.
k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 5) yeni bir çerçeve açar (kok = 5).
kok ≠ null. k = compareTo = 0 çünkü 5 = 5.
k == 0 → anahtar bulundu: return true. Bu çerçeve kapanır.
ara(5, 5) çağrısı true döndürdü; 4'teki çerçeve bu değeri aynen return eder.
ara(4, 5) çağrısı true döndürdü; 6'daki çerçeve bu değeri aynen return eder.
main sonucu alır: true. 3 düğümle karşılaştırıldı = bulunan düğümün derinliği + 1.
ara(kok, 10) çağrıldı; kok kökü (6) gösterir. kok ≠ null. k = compareTo = 1 çünkü 10 > 6.
k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = 8).
kok ≠ null. k = compareTo = 1 çünkü 10 > 8.
k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = 9).
kok ≠ null. k = compareTo = 1 çünkü 10 > 9.
k > 0 → sağ alt ağaçta ara: ara(kok.sagCocuk, 10) yeni bir çerçeve açar (kok = null).
kok == null: 9'un sağ çocuğu yok, yani 10 ağaçta yok → return false.
ara(null, 10) çağrısı false döndürdü; 9'daki çerçeve bu değeri aynen return eder.
ara(9, 10) çağrısı false döndürdü; 8'deki çerçeve bu değeri aynen return eder.
ara(8, 10) çağrısı false döndürdü; 6'daki çerçeve bu değeri aynen return eder.
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).
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);
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);
main, kok = ekle(kok, 5) çağırır. Ekleme de aramada olduğu gibi kökten başlar.
kok ≠ null (6). k = -1 çünkü 5 < 6.
Sol alt ağaca in: ekle(kok.solCocuk, 5); kok = 2. Dönen değer kok.solCocuk'a atanacak.
kok ≠ null (2). k = 1 çünkü 5 > 2.
Sağ alt ağaca in: ekle(kok.sagCocuk, 5); kok = 4. Dönen değer kok.sagCocuk'a atanacak.
kok ≠ null (4). k = 1 çünkü 5 > 4.
Sağ alt ağaca in: ekle(kok.sagCocuk, 5); kok = null. Dönen değer kok.sagCocuk'a atanacak.
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.
4'ün çerçevesine dönüldü: kok.sagCocuk = düğüm(5). Null olan bağın yerine yeni düğüm bağlandı (turuncu).
return kok: 4, kendi ebeveynine (2) geri döndürülür.
2'de kok.sagCocuk = düğüm(4): bağ zaten 4'ü gösteriyordu, değişmez. Sonra return 2.
6'da kok.solCocuk = düğüm(2) (aynı bağ), sonra return 6.
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)
| İşlem | Anlamı | Java TreeMap | Yukarıdaki ağaçta |
|---|---|---|---|
| ardıl | x'ten büyük en küçük anahtar | higherKey(x) | ardil(5) = 6 |
| öncül | x'ten küçük en büyük anahtar | lowerKey(x) | oncul(6) = 5 |
| tavan (ceiling) | ≥ x olan en küçük anahtar | ceilingKey(x) | tavan(3) = 4 |
| taban (floor) | ≤ x olan en büyük anahtar | floorKey(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)
ardil(kok, 5): henüz aday yok (aday = null); kökten (6) başla.
5 < 6 → 6 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in.
5 ≥ 4 → 4 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in.
5 ≥ 5 → 5 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in. kok = null oldu.
Döngü bitti (kok == null) → return düğüm(6): ardil(5) = 6.
İkinci çağrı: ardil(kok, 6): henüz aday yok (aday = null); kökten (6) başla.
6 ≥ 6 → 6 ve solundakiler ardıl olamaz; x'ten büyükler sağda → sağa in.
6 < 8 → 8 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in.
6 < 7 → 7 bir aday (x'ten büyük); daha küçük bir aday ancak solda olabilir → sola in. kok = null oldu.
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:
null yapılır.sil 70Örnek ağaç
BST İşlemleri
Silme: Yaprak ve Tek Çocuk
Yaprak: 70'in çocuğu yok. Ebeveyni 60'ın sağ bağı null yapılır.
70 silindi; ağacın geri kalanı değişmedi.
Tek çocuk (sol): 45'in yalnızca sol çocuğu 42 var. (Orijinal ağaçtan başlıyoruz.)
42, 45'in yerine 40'ın sağ çocuğu olur: ebeveyn doğrudan toruna bağlanır.
Tek çocuk (sağ): 60'ın yalnızca sağ çocuğu 70 var.
70, 60'ın yerine 50'nin sağ çocuğu olur. Sıralama düzeni korunur.
BST İşlemleri
Silme: İki Çocuklu Düğüm
İki çocuk: 40'ın hem solu (30) hem sağı (45) var; doğrudan çıkarılamaz.
İnorder ardıl = sağ alt ağacın en küçüğü: 45'e geç, sonra hep sola → 42.
Ardılın değeri (42), 40'ın düğümüne kopyalanır. Şimdi eski 42 fazladan duruyor.
Sağ alt ağaçtan eski 42 silinir; ardılın sol çocuğu olmadığı için bu hep Durum 1 veya 2'dir.
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;
}
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;
}
main, kok = sil(kok, 70) çağırır. Önce silinecek düğüm aranır.
kok ≠ null. k = 1 çünkü 70 > 50.
Sağ alt ağaçta sil: sil(kok.sagCocuk, 70). Dönen değer kok.sagCocuk'a atanacak.
kok ≠ null. k = 1 çünkü 70 > 60.
Sağ alt ağaçta sil: sil(kok.sagCocuk, 70). Dönen değer kok.sagCocuk'a atanacak.
kok ≠ null. k = 0 çünkü 70 = 70.
k = 0 → silinecek düğüm bulundu. Sol çocuk yok → return kok.sagCocuk = null. (Yaprak: null döner.)
Değişen bağ: kok.sagCocuk = null. Silinen düğüme artık hiçbir bağ yok; çöp toplayıcı temizler.
return kok: 60 kendi ebeveynine döndürülür.
kok.sagCocuk = düğüm(60): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).
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;
}
main, kok = sil(kok, 45) çağırır. Önce silinecek düğüm aranır.
kok ≠ null. k = -1 çünkü 45 < 50.
Sol alt ağaçta sil: sil(kok.solCocuk, 45). Dönen değer kok.solCocuk'a atanacak.
kok ≠ null. k = 1 çünkü 45 > 40.
Sağ alt ağaçta sil: sil(kok.sagCocuk, 45). Dönen değer kok.sagCocuk'a atanacak.
kok ≠ null. k = 0 çünkü 45 = 45.
k = 0 → bulundu. Sol çocuk var, sağ çocuk yok → return kok.solCocuk = düğüm(42): tek çocuk ebeveyne verilir.
Değişen bağ: kok.sagCocuk = 42. Ebeveyn, silinen düğümü atlayıp doğrudan onun çocuğuna bağlandı.
return kok: 40 kendi ebeveynine döndürülür.
kok.solCocuk = düğüm(40): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).
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;
}
main, kok = sil(kok, 40) çağırır. Önce silinecek düğüm aranır.
kok ≠ null. k = -1 çünkü 40 < 50.
Sol alt ağaçta sil: sil(kok.solCocuk, 40). Dönen değer kok.solCocuk'a atanacak.
kok ≠ null. k = 0 çünkü 40 = 40.
k = 0 → bulundu, ama iki çocuğu var (30 ve 45): doğrudan çıkarılamaz.
minDeger(kok.sagCocuk): sağa bir adım (45), sonra hep sola → ardıl = 42 (sağ alt ağacın en küçüğü).
kok.veri = 42: ardılın değeri silinecek düğüme kopyalanır. Şimdi 42 iki kez var; alttakini silmeliyiz.
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.
kok ≠ null. k = -1 çünkü 42 < 45.
Sol alt ağaçta sil: sil(kok.solCocuk, 42). Dönen değer kok.solCocuk'a atanacak.
kok ≠ null. k = 0 çünkü 42 = 42.
k = 0 → silinecek düğüm bulundu. Sol çocuk yok → return kok.sagCocuk = null. (Yaprak: null döner.)
Değişen bağ: kok.solCocuk = null. Silinen düğüme artık hiçbir bağ yok; çöp toplayıcı temizler.
return kok: 45 kendi ebeveynine döndürülür.
kok.sagCocuk = düğüm(45) (aynı bağ), return kok. Silinen değer 40'ın yerini 42 aldı.
kok.solCocuk = düğüm(42): bağ zaten onu gösteriyordu, değişmez. Sonra return kok (50).
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).
| Durum | Yükseklik h | Arama / Ekleme / Silme | n = 106 için adım |
|---|---|---|---|
| Dengeli (en iyi) | ≈ log2 n | O(log n) | ≈ 20 |
| Rastgele sıra (ortalama) | ≈ 2,99 log2 n | O(log n) | ≈ 28 (ortalama) |
| Devrik (en kötü, sıralı girdi) | n − 1 | O(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)
aralik(8): 5 < 8 → solda aralığa giren anahtar olabilir: aralik(k.sol).
aralik(4): 5 < 4 yanlış → sol alt ağaçtaki her anahtar < 4 ≤ lo: sol alt ağaç budanır (hiç ziyaret edilmez).
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.
aralik(6): 5 < 6 → solda aralığa giren anahtar olabilir: aralik(k.sol).
aralik(5): 5 < 5 yanlış → sol alt ağaçtaki her anahtar < 5 ≤ lo: sol alt ağaç budanır (hiç ziyaret edilmez).
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.
aralik(null): boş alt ağaç → hemen return.
Sol taraf bitti, aralik(6)'ya dönüldü. 6 ∈ [5, 10] → yazdır. 6 < 10 → sağa da bak.
aralik(7): 5 < 7 → solda aralığa giren anahtar olabilir: aralik(k.sol).
aralik(null): boş alt ağaç → hemen return.
Sol taraf bitti, aralik(7)'ye dönüldü. 7 ∈ [5, 10] → yazdır. 7 < 10 → sağa da bak.
aralik(null): boş alt ağaç → hemen return.
Sol taraf bitti, aralik(8)'e dönüldü. 8 ∈ [5, 10] → yazdır. 8 < 10 → sağa da bak.
aralik(12): 5 < 12 → solda aralığa giren anahtar olabilir: aralik(k.sol).
aralik(10): 5 < 10 → solda aralığa giren anahtar olabilir: aralik(k.sol).
aralik(null): boş alt ağaç → hemen return.
Sol taraf bitti, aralik(10)'a dönüldü. 10 ∈ [5, 10] → yazdır. 10 ≥ 10 → sağ alt ağaç budanır.
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.
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()
bstMi(6): 6 ∈ (−∞, ∞) ✓. Sol alt ağaç için üst sınır 6 olur: bstMi(k.sol, −∞, 6).
bstMi(2): 2 ∈ (−∞, 6) ✓. Sol alt ağaç için üst sınır 2 olur: bstMi(k.sol, −∞, 2).
bstMi(1): 1 ∈ (−∞, 2) ✓. Yaprak: iki çocuğu da null → ikisi de true döner → return true.
Sol taraf true. Sağ alt ağaç için alt sınır 2 olur: bstMi(k.sag, 2, 6).
bstMi(4): 4 ∈ (2, 6) ✓. Sol alt ağaç için üst sınır 4 olur: bstMi(k.sol, 2, 4).
bstMi(3): 3 ∈ (2, 4) ✓. Yaprak: iki çocuğu da null → ikisi de true döner → return true.
Sol taraf true. Sağ alt ağaç için alt sınır 4 olur: bstMi(k.sag, 4, 6).
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ı.
Sağdan false geldi → bstMi(4) = true && false = false.
Sağdan false geldi → bstMi(2) = true && false = false.
Sol sonuç false → && kısa devre yapar: sağ alt ağaç hiç denetlenmez, bstMi(6) false döner.
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 } }
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).
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:
- 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.
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.
h = 0: tek düğüm. N(0) = 1.
h = 1: kök + tek çocuk. N(1) = 2.
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.
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.
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.
Denge ve AVL
Döndürme (Rotation)
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.
Sola döndürme (sağ ağır x): simetrik. Yalnızca 3 bağ değişir (kalın çizgiler) → O(1).
Denge ve AVL
Tek Döndürmeler: LL ve RR
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.
30'da sağa döndür: 20 yukarı çıkar, 30 onun sağ çocuğu olur (turuncu bağ). Tüm bf = 0.
RR: 30, sağ çocuğun (20) sağına eklendi. bf(10) = −2 ✗ ve bf(20) = −1: ikisi de sağa eğik.
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
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.
1. döndürme: 10'da sola döndür → 20 yukarı; üçgen düz çizgiye (LL) döndü. bf(30) hâlâ 2.
2. döndürme: 30'da sağa döndür → ortanca anahtar 20 kök oldu; tüm bf = 0.
RL: 20, sağ çocuğun (30) soluna eklendi. bf(10) = −2, bf(30) = 1: üçgen.
1. döndürme: 30'da sağa döndür → düz çizgi (RR). bf(10) hâlâ −2.
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));
}
5 eklendi; dengele(40) bf = 2 buldu, sol çocuğu 20'de bf = 1 → LL. sagaDondur(y) çağrılır.
x = y.sol: yukarı çıkacak çocuk 20. Henüz hiçbir bağ değişmedi.
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.
x.sag = y: x.sag: 30 → 40. Artık 40, 20'nin sağ çocuğu (turuncu ok yukarı doğru).
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ı.
guncelle(x): 20.yukseklik = 1 + max(1, 1) = 2 (değişmedi, ama yeniden hesaplanmalı).
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));
}
60 eklendi; dengele(20) bf = −2 buldu, sağ çocuğu 40'ta bf = −1 → RR. solaDondur(x) çağrılır.
y = x.sag: yukarı çıkacak çocuk 40. Henüz hiçbir bağ değişmedi.
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.
y.sol = x: y.sol: 30 → 20. Artık 20, 40'ın sol çocuğu (turuncu ok yukarı doğru).
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ı.
guncelle(y): 40.yukseklik = 1 + max(1, 1) = 2 (değişmedi, ama yeniden hesaplanmalı).
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;
}
25 eklendikten sonra özyineleme geri dönerken dengele(30) çağrılır. guncelle: yükseklik 0 → 1, bf = 1.
bf = 1: ne > 1 ne < −1 → döndürme yok, return d. Bir üst çağrıya dönülür.
25 eklendikten sonra özyineleme geri dönerken dengele(20) çağrılır. guncelle: yükseklik 1 → 2, bf = −1.
bf = −1: ne > 1 ne < −1 → döndürme yok, return d. Bir üst çağrıya dönülür.
dengele(50): guncelle → yükseklik 3, bf = h(20) − h(60) = 2 − 0 = 2.
bf(d) = 2 > 1 → sol ağır. Hangi döndürme? Sol çocuğun eğimine bakılır.
bf(d.sol) = bf(20) = -1 < 0: sol çocuk sağa eğik → LR (üçgen). Tek sağa döndürme yetmez.
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.
return sagaDondur(50): 30 kök olur, 50 onun sağına iner; 30'un eski sağı (null) 50'nin soluna geçer.
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
Ağaç boş: ekle(null, 10) → d == null, yeni düğüm döner; kök = 10, bf = 0.
ekle(kok, 20) BST gibi iner: 20 > 10 → sağ.
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.
Dönüşte her atada return dengele(d): bf(10) = −1 → hepsinde |bf| ≤ 1, döndürme yok.
ekle(kok, 30) BST gibi iner: 30 > 10 → sağ; 30 > 20 → sağ.
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.
dengele(10): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 20'de bf = −1 → RR durumu.
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
ekle(kok, 40) BST gibi iner: 40 > 20 → sağ; 40 > 30 → sağ.
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.
Dönüşte her atada return dengele(d): bf(30) = −1, bf(20) = −1 → hepsinde |bf| ≤ 1, döndürme yok.
ekle(kok, 50) BST gibi iner: 50 > 20 → sağ; 50 > 30 → sağ; 50 > 40 → sağ.
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.
dengele(30): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 40'ta bf = −1 → RR durumu.
30'da solaDondur: 40 yukarı çıkar, 30 onun sol çocuğu olur. Tüm bf ∈ {−1, 0, 1}.
ekle(kok, 25) BST gibi iner: 25 > 20 → sağ; 25 < 40 → sol; 25 < 30 → sol.
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.
dengele(20): bf = −2 ✗ (en alttaki dengesiz ata). Sağ çocuk 40'ta bf = 1 → RL durumu.
1. döndürme: 40'ta sagaDondur → üçgen düz çizgiye (sağ-sağ) döner. bf(20) hâlâ −2.
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)
- Her düğüm kırmızı ya da siyahtır.
- Kök siyahtır.
- Tüm yapraklar (NIL, boş çocuklar) siyah kabul edilir.
- Kırmızı düğümün çocukları siyahtır: ardışık iki kırmızı olmaz.
- Bir düğümden NIL yapraklara giden her yolda aynı sayıda siyah düğüm vardır (siyah yükseklik).
Ö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?
- 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.
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:
- 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).
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; }
10 BST gibi eklendi (kök) ve kırmızı boyandı; duzelt(x) çağrılır.
x kökün kendisi → döngü koşulu yanlış, döngüye girilmez.
kok.renk = SIYAH: kök her zaman siyah (kural 2).
20 BST gibi eklendi (10'un sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.
x.ebeveyn (10) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.
30 BST gibi eklendi (20'nin sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.
x.ebeveyn (20) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.
p = ebeveyn (20), g = dede (10), u = amca = NIL (boş, siyah sayılır).
Amca siyah → döndürme gerekir. x–p–g düz çizgi: ucgen() yanlış, doğrudan satır 14.
dondur(g, p): dede 10'da sola döndürme → p (20) yukarı çıkar, g onun çocuğu olur.
Renkler: p siyah, g kırmızı. Ardışık kırmızı kalmadı, siyah yükseklik korundu.
x.ebeveyn (20) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.
15 BST gibi eklendi (10'un sağ çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.
x.ebeveyn (10) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.
p = ebeveyn (10), g = dede (20), u = amca = 30.
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.
x = g (20): g kırmızı oldu; onun ebeveyni de kırmızı olabilir → döngü dededen devam eder.
x kökün kendisi → döngü koşulu yanlış, döngüye girilmez.
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; }
12 BST gibi eklendi (15'in sol çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.
x.ebeveyn (15) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.
p = ebeveyn (15), g = dede (10), u = amca = NIL (boş, siyah sayılır).
Amca siyah → döndürme gerekir. x–p–g üçgen (zikzak): x ile p zıt yönde çocuk.
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ı).
x ile p rolleri değişir: artık x = 15 (alttaki), p = 12.
dondur(g, p): dede 10'da sola döndürme → p (12) yukarı çıkar, g onun çocuğu olur.
Renkler: p siyah, g kırmızı. Ardışık kırmızı kalmadı, siyah yükseklik korundu.
x.ebeveyn (12) siyah → ardışık kırmızı yok, döngü biter. Satır 18: kök zaten siyah.
5 BST gibi eklendi (10'un sol çocuğu) ve kırmızı boyandı; duzelt(x) çağrılır.
x.ebeveyn (10) de kırmızı: ardışık iki kırmızı ✗ (kural 4) → döngüye girilir.
p = ebeveyn (10), g = dede (12), u = amca = 15.
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.
x = g (12): g kırmızı oldu; onun ebeveyni de kırmızı olabilir → döngü dededen devam eder.
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
| Yapı | Yukarı doğru inceleme | Döndürme | Toplam |
|---|---|---|---|
| AVL | O(log n) | O(log n) olabilir | O(log n) |
| Kırmızı-siyah | O(log n) | en fazla 3 | O(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ı?
| Özellik | AVL | Kırmızı-Siyah |
|---|---|---|
| Denge ölçütü | |bf| ≤ 1 her düğümde | renk kuralları, eşit siyah yükseklik |
| En büyük yükseklik | ≈ 1,44 log2 n | 2 log2(n + 1) |
| Arama | O(log n), biraz daha hızlı (daha kısa) | O(log n) |
| Ekleme döndürmesi | en fazla 2 (bir tek veya bir çift) | en fazla 2 |
| Silme döndürmesi | O(log n) olabilir | en fazla 3 |
| Düğümde ek bilgi | yükseklik (veya bf) | 1 bit renk |
| Tercih | aramanın baskın olduğu işler | sı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).
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.
Zig: x'in ebeveyni p köktür → p'de tek döndürme. (Yalnızca son adımda olabilir.)
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ı!)
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); } } }
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.)
x kök değil. p = 60, g = 40.
x ve p zıt yönde → zig-zag. Önce ebeveyn: 60'ta sağa döndür.
Sonra dede: 40'ta sola döndür → x, p ile g'nin ebeveyni olur.
x kök değil. p = 80, g = null (p kök).
g == null → zig: kök 80'de sağa döndürme; x kök olur.
x == kok → döngü biter. 50 artık kök; sık erişilen anahtarlar köke yakın durur.
splay(10): önce normal BST araması 50 → 40 → 20 → 10; bulunan düğüm x olur.
x kök değil. p = 20, g = 40.
x ve p aynı yönde → zig-zig. Önce dede: 40'ta sağa döndür (p yukarı).
Sonra ebeveyn: 20'de sağa döndür → x iki düzey yükseldi.
x kök değil. p = 50, g = null (p kök).
g == null → zig: kök 50'de sağa döndürme; x kök olur.
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.
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
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.
Yaprakta 30 bulunur. Her düzeyde yalnızca bir düğüm (bir disk bloğu) okunur → maliyet yükseklikle orantılı: O(logm n).
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ı.
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.
Ç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 eklenir. kök yaprağa sıralı yerine konur; düğümde 1 anahtar var (≤ 2), bölünme gerekmez.
20 eklenir. kök yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.
30 eklenir → düğüm [10, 20, 30] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.
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.
40 eklenir. Kökten sağa inilir; yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.
50 eklenir → düğüm [30, 40, 50] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.
Ortanca 40 ebeveyne çıkar; kalanlar 30 ve 50 olarak iki düğüme ayrılır. Ebeveynde yer var → bölünme burada durur.
60 eklenir. Kökten sağa inilir; yaprağa sıralı yerine konur; düğümde 2 anahtar var (≤ 2), bölünme gerekmez.
70 eklenir → düğüm [50, 60, 70] oldu: 3 anahtar, kapasite (m − 1 = 2) aşıldı → bölünmeli.
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.
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.
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)
Ç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ı
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.)
İlk yaprak [5 10]: 5 < 10 atlanır, 10 alınır. Yaprağın sonuna gelindi → sonraki yaprak bağına geç (yukarı çıkmadan).
İkinci yaprak: 20, 25, 30 hepsi aralıkta → alınır. Yine bağı izle.
Üçüncü yaprak: 40 alınır; 50 > 45 → dur. Sonuç: 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ükseklik | Arama | Ekleme | Silme | Nasıl dengelenir? |
|---|---|---|---|---|---|
| BST | log n … n | O(h) | O(h) | O(h) | dengelenmez; en kötü O(n) |
| AVL | ≤ 1,44 log n | O(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 |
| Splay | sınırsız | O(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 n | O(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).
Özet
Etkileşimli Simülatörler
Özet
Sık Yapılan Hatalar
sil(kok, 40); yazmak kök silindiğinde ağacı bozar. Doğrusu: kok = sil(kok, 40); (ekle için de aynı).bstMi).== ile karşılaştırmakReferansı karşılaştırır. Anahtarlar compareTo ile, eşitlik equals ile sınanır.h(null), kirmizi(null) gibi yardımcılar bu yüzden var.Özet
Örnek Problemler
- Boş BST'ye 50, 30, 70, 20, 40, 60, 80 sırayla eklenir. Yükseklik nedir? Kök ortada gezinme çıktısı nedir?
- Aynı anahtarlar 20, 30, 40, 50, 60, 70, 80 sırasıyla eklenirse yükseklik ve arama maliyeti ne olur?
- 1. sorudaki ağaçtan kök 50, kök ortada ardıl kullanılarak silinirse ağaç nasıl olur?
- Boş AVL ağacına 3, 2, 1 eklenir. Hangi durum oluşur, hangi döndürme yapılır?
- Boş AVL ağacına 10, 30, 20 eklenir. Hangi durum oluşur, sonuç ağaç nedir?
- 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?
- Splay ağacında zig-zig adımında döndürmeler hangi sırayla yapılır?
Cevapları göster
- h = 2 (tam dengeli); kök ortada: 20 30 40 50 60 70 80.
- Sağa doğru zincir: h = 6; arama O(n) (en kötü 7 karşılaştırma).
- Ardıl 60 köke kopyalanır, 70'in solundaki 60 silinir: 60(30(20, 40), 70(—, 80)).
- 3'te bf = 2, sol çocuk 2'de bf = 1 → LL; 3'te sağa döndürme → 2(1, 3).
- 10'da bf = −2, sağ çocuk 30'da bf = +1 → RL; 30'da sağa, 10'da sola → 20(10, 30).
- 2 katı: siyah sayısı her yolda eşit ve kırmızılar ardışık olamaz.
- Önce dede (g), sonra ebeveyn (p) döndürülür.
Özet
Alıştırmalar
taban(kok, x): ≤ x olan en büyük anahtarı (yoksa null) döndüren metodu yazın.- BST'de k. en küçük anahtarı bulan bir metot yazın. Zaman karmaşıklığı nedir?
- 1, 2, …, 7 boş bir BST'ye hangi sırayla eklenirse yüksekliği 2 olan mükemmel ağaç oluşur?
- 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?
- Yüksekliği 5 olan bir AVL ağacı en az kaç düğüm içerir?
aralik(kok, lo, hi)neden O(n) değil de O(h + s) sürer?
Cevaplar / ipuçları
ardil'in simetriği:x >= kok.veriiseaday = kok; kok = kok.sag;değilse sola. O(h).- Sayaçlı kök ortada gezinme: k. ziyaret edilen düğüm cevaptır → O(h + k).
- Önce orta, sonra yarıların ortası: ör. 4, 2, 6, 1, 3, 5, 7.
- 4(2(1, 3), 6(5, 7)); 3, 5, 6 ve 7 eklenirken birer sola döndürme → 4 döndürme.
- N(5) = 1 + N(4) + N(3) = 1 + 12 + 7 = 20.
- 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.