AVL ağaçları: denge faktörü ve rotasyonlar
BST'yi O(log n)'de tutan AVL kuralı, LL/RR/LR/RL rotasyonları ve sınavın klasik sorusu: bu sırayla eklenince ağaç neye benzer?
Geçen hafta gördüğün ikili arama ağacı (BST) güzel ama kırılgan. Anahtarları sıralı eklersen (1, 2, 3, …) ağaç sağa doğru bir zincire döner ve arama O(n) olur. AVL ağacı (Adelson-Velsky ve Landis) bu sorunu çözen ilk kendini dengeleyen (self-balancing) BST’dir. Sınavda neredeyse her zaman aynı soru gelir: “Şu anahtarları boş bir AVL ağacına sırayla ekle, son ağacı çiz.” Bu sayfadaki dört rotasyonu ezberden değil, mantığıyla bilen biri bu soruyu kaçırmaz.
Altın kurallar:
- AVL kuralı: her düğümde sol ve sağ alt ağacın yükseklik farkı en fazla 1.
- Yükseklik: boş ağaç -1, yaprak 0. Denge faktörü (balance factor) bf = height(sol) - height(sağ).
- Ekledikten sonra yukarı çık. bf değeri +2 ya da -2 olan ilk (en derindeki) düğümde dur ve düzelt. Eklemede tek düzeltme yeter.
- Yön sorusu: yeni anahtar o düğümden itibaren hangi iki adımı attı? Sol-sol, sağ-sağ, sol-sağ, sağ-sol. Dört durumun adı buradan gelir.
Yükseklik ve denge faktörü
Bir düğümün yüksekliği (height), ondan en uzak yaprağa giden yoldaki kenar sayısıdır. Hesabı her zaman alttan yukarı yap:
30 h=2, bf=1-0=1
/ \
20 40 h(20)=1 bf=1, h(40)=0
/
10 h=0, bf=0
Bu ağaç AVL: bütün bf değerleri -1, 0 ya da 1. 10’un altına 5 eklersen 20’nin bf’si 2 olur ve kural bozulur.
TUZAK · Yükseklik ile derinlik karışmasın. Derinlik (depth) kökten aşağı sayılır, yükseklik yapraktan yukarı. bf hesabında hep yükseklik kullanılır. Bazı kaynaklar yaprağa 1 der; o zaman tüm değerler 1 kayar ama bf aynı çıkar. Sınavda hocanın tanımına bak.
Neden O(log n)?
En az düğümle h yüksekliğinde AVL ağacı kurmak istersen, bir alt ağacı h-1, diğerini h-2 yüksekliğinde yaparsın (fark 1’e izin var). Buradan:
| h | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| N(h) en az düğüm | 1 | 2 | 4 | 7 | 12 | 20 | 33 | 54 |
Bu dizi Fibonacci gibi üstel büyür. Düğüm sayısı yükseklikle üstel artıyorsa, yükseklik düğüm sayısıyla logaritmik artar: . Arama, ekleme ve silme yükseklik kadar adım attığı için hepsi O(log n). Rotasyon sadece birkaç işaretçi değiştirir, O(1).
Dört durum ve rotasyonlar
Ekleme normal BST eklemesi gibi yapılır. Sonra eklenen yaprağın atalarına yukarı doğru bakılır. İlk dengesiz düğüme k diyelim. Yeni anahtarın k’dan sonra attığı iki adım durumu belirler.
LL ve RR: tek rotasyon
LL durumunda yeni anahtar k’nın sol çocuğunun sol alt ağacına gitmiştir. Sol çocuk (k1) yukarı çıkar, k2 sağa iner. X, Y, Z alt ağaçlardır:
LL: k2'de saga rotasyon
k2 k1
/ \ / \
k1 Z ==> X k2
/ \ / \
X Y Y Z
Dikkat: k1’in sağındaki Y alt ağacı k2’nin soluna geçer. Y’deki her anahtar k1’den büyük, k2’den küçüktür. Yani yeni yerinde de BST sırası korunur. Inorder sıra X, k1, Y, k2, Z her iki çizimde aynıdır.
RR bunun ayna görüntüsüdür: k1 üzerinde sola rotasyon, sağ çocuk k2 köke çıkar, k2’nin sol alt ağacı k1’in sağına geçer.
RR: 10'da sola rotasyon (10, 20, 30 eklendi)
10 20
\ / \
20 ==> 10 30
\
30
LR ve RL: çift rotasyon
LR durumunda yeni anahtar k’nın solunun sağına gitmiştir. Tek sağa rotasyon burada işe yaramaz, sadece eğriliği diğer tarafa taşır. Çözüm iki adım: önce sol çocukta sola rotasyon (durumu LL’ye çevirir), sonra k’da sağa rotasyon. Sonuçta ortadaki düğüm (k1 ile k3 arasındaki) en üste çıkar.
LR: 30, 10, 20 eklendi
30 30 20
/ / / \
10 ==> 20 ==> 10 30
\ /
20 10
10'da sola 30'da saga
RL ayna görüntüsü: önce sağ çocukta sağa, sonra k’da sola rotasyon.
İPUCU · Çift rotasyonun kısa yolu. k, çocuğu ve torunu (yeni anahtarın yolundaki üç düğüm) sıraya diz. Ortanca değer yeni kök olur, küçüğü sola, büyüğü sağa gider. Torunun eski iki alt ağacı sırayla sol ve sağ tarafa dağıtılır.
| Durum | Yeni anahtarın yolu | bf(k) | Düzeltme |
|---|---|---|---|
| LL | sol, sol | +2 | k’da tek sağa rotasyon |
| RR | sağ, sağ | -2 | k’da tek sola rotasyon |
| LR | sol, sağ | +2 | sol çocukta sola, sonra k’da sağa |
| RL | sağ, sol | -2 | sağ çocukta sağa, sonra k’da sola |
Java’da AVL ekleme
Düğüm kendi yüksekliğini tutar. Ekleme özyinelemelidir: geri dönerken her düğüm yüksekliğini günceller ve gerekirse dengelenir. balance metodu yükseklikleri karşılaştırdığı için hem ekleme hem silme için çalışır.
static class AvlNode {
int key, height; // yaprak: height = 0
AvlNode left, right;
AvlNode(int key) { this.key = key; }
}
static int height(AvlNode t) { return t == null ? -1 : t.height; }
static void update(AvlNode t) {
t.height = 1 + Math.max(height(t.left), height(t.right));
}
static AvlNode rotateRight(AvlNode k2) { // LL
AvlNode k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
update(k2);
update(k1);
return k1;
}
static AvlNode rotateLeft(AvlNode k1) { // RR
AvlNode k2 = k1.right;
k1.right = k2.left;
k2.left = k1;
update(k1);
update(k2);
return k2;
}
static AvlNode insert(AvlNode t, int x) {
if (t == null) return new AvlNode(x);
if (x < t.key) t.left = insert(t.left, x);
else if (x > t.key) t.right = insert(t.right, x);
else return t; // kopya eklenmez
return balance(t);
}
static AvlNode balance(AvlNode t) {
update(t);
int bf = height(t.left) - height(t.right);
if (bf > 1) {
if (height(t.left.left) < height(t.left.right))
t.left = rotateLeft(t.left); // LR: once LL'ye cevir
return rotateRight(t);
}
if (bf < -1) {
if (height(t.right.right) < height(t.right.left))
t.right = rotateRight(t.right); // RL
return rotateLeft(t);
}
return t;
}
Kullanım hep root = insert(root, x); biçimindedir. Rotasyon alt ağacın kökünü değiştirdiği için dönen değeri atamak şart.
Çözümlü örnek: 15, 20, 24, 10, 13, 7, 30, 36, 25
Boş bir AVL ağacına bu dokuz anahtarı ekleyelim. Her adımda yolu takip et, yukarı doğru bf kontrol et.
+15, +20: sorun yok. +24: 15’in bf’si -2, yol sağ-sağ: RR, 15’te sola rotasyon.
15 20
\ / \
20 ==> 15 24
\
24
+10: 15’in soluna, dengeli. +13: 13 → 20 → 15 → 10 → sağ. 15’in bf’si 2 (sol yükseklik 1, sağ -1). Yol sol-sağ: LR. 10’da sola, 15’te sağa. 13 ortaya çıkar.
20 20
/ \ / \
15 24 ==> 13 24
/ / \
10 10 15
\
13
+7: 7 → 20 → 13 → 10 → sol. Yukarı bak: 10’un bf’si 1, 13’ün bf’si 1, 20’nin bf’si height(13) - height(24) = 2 - 0 = 2. Yol (20’den) sol-sol: LL, 20’de sağa rotasyon. 13 köke çıkar, 13’ün sağ çocuğu 15, 20’nin soluna geçer.
13
/ \
10 20
/ / \
7 15 24
+30: 24’ün sağına, dengeli. +36: 24’ün bf’si -2, sağ-sağ: RR, 24’te sola rotasyon, 30 yukarı çıkar.
13
/ \
10 20
/ / \
7 15 30
/ \
24 36
+25: 25 → 13 → 20 → 30 → 24 → sağ. 24’ün bf’si -1, 30’un bf’si 1, 20’nin bf’si 0 - 2 = -2. Yol (20’den) sağ-sol: RL. Önce 30’da sağa rotasyon (24 yukarı), sonra 20’de sola rotasyon (24, 20’nin yerine geçer).
13
/ \
10 24
/ / \
7 20 30
/ / \
15 25 36
Program çıktısı (her eklemeden sonra seviye sırası ve yapılan düzeltme):
+15 -> 15
+20 -> 15 20
+24 -> 20 15 24 [RR at 15]
+10 -> 20 15 24 10
+13 -> 20 13 24 10 15 [LR at 15]
+7 -> 13 10 20 7 15 24 [LL at 20]
+30 -> 13 10 20 7 15 24 30
+36 -> 13 10 20 7 15 30 24 36 [RR at 24]
+25 -> 13 10 24 7 20 30 15 25 36 [RL at 20]
Son ağacın yüksekliği 3. Kökün bf’si 1 - 2 = -1, kural tutuyor.
Silme kısaca
AVL’den silme önce normal BST silmesidir (iki çocuklu düğümde sağ alt ağacın en küçüğü, inorder successor, yerine geçer). Sonra silinen yerden köke kadar her düğümde balance çağrılır. Eklemeden farkı: silmede bir rotasyon alt ağacın yüksekliğini azaltabilir ve dengesizlik yukarıya taşınabilir. Yani köke kadar birden fazla rotasyon gerekebilir. Durum seçimi yine çocukların yüksekliğine bakılarak yapılır; yukarıdaki balance metodu bunu zaten yapıyor. Süre yine O(log n).
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Kökten başlayıp ilk dengesiz düğümü düzeltirim | Yeni yapraktan yukarı çık; en derindeki dengesiz düğümü düzelt |
| LR’de tek sağa rotasyon yeter | Tek rotasyon eğriliği aynaya çevirir; çift rotasyon gerekir |
| Rotasyonda Y alt ağacı kaybolur ya da yerinde kalır | Y her zaman yukarı çıkan düğümden aşağı inen düğüme geçer |
| Yaprağın yüksekliği 1, boş ağaç 0 | Bu derste: yaprak 0, null -1 |
| Eklemeden sonra birkaç rotasyon gerekebilir | Eklemede en fazla bir (tek ya da çift) düzeltme yeter; silmede birden çok olabilir |
rotateRight(t); yazmak yeterli | Dönen yeni kökü ata: t = rotateRight(t); |
| Yükseklikleri herhangi bir sırada güncellerim | Önce aşağı inen düğüm, sonra yeni kök |
| bf’yi alt ağaçtaki düğüm sayısıyla hesaplarım | bf yükseklik farkıdır, düğüm sayısı değil |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
In the tree below, what is the balance factor of the root (height(left) - height(right)), and is the tree an AVL tree?
20
/ \
10 30
/ \
5 15
/
3Cevabı göster
Yükseklikleri alttan hesapla (boş = -1, yaprak = 0). 3: 0. 5: 1. 15: 0. 10: 1 + max(1, 0) = 2. 30: 0.
Kök: height(10) - height(30) = 2 - 0 = 2. |bf| > 1 olduğu için AVL değil. 10 ve 5 düğümlerinin bf değeri 1, onlar sorun değil.
Insert 10, 20, 30 (in this order) into an empty AVL tree. Which key is the root, and which rotation was used?
Cevabı göster
10 ve 20 sorunsuz. 30 eklenince 10'un bf'si -2 olur. Yeni anahtar 10'un sağının sağına gitti: RR durumu.
10 üzerinde tek sola rotasyon: 20 köke çıkar, 10 sol, 30 sağ çocuk olur.
Keys 30, 10, 20 are inserted into an empty AVL tree in this order. Which case occurs at node 30 and what kind of rotation fixes it?
Cevabı göster
20, 30'un solunun (10) sağına gidiyor: sol-sağ, yani LR. Tek rotasyon bunu düzeltmez.
Önce 10 üzerinde sola rotasyon (20 yukarı çıkar), sonra 30 üzerinde sağa rotasyon. Sonuç: kök 20, çocuklar 10 ve 30.
What is the worst-case time complexity of search and insert in an AVL tree with n nodes? Compare with an ordinary BST.
Cevabı göster
AVL ağacının yüksekliği her zaman yaklaşık 1.44 log2 n'den küçük kalır. Arama ve ekleme yükseklik kadar adım atar, rotasyonlar O(1). Bu yüzden O(log n).
Sıradan BST sıralı veriyle (1, 2, 3, ...) bağlı listeye döner, yükseklik n - 1 olur: O(n).
Insert 1, 2, 3, 4, 5, 6, 7 into an empty AVL tree. Give the level-order traversal of the final tree and the number of rotations performed.
Cevabı göster
Hepsi RR durumu, hepsi tek sola rotasyon:
+3: 1'de dengesizlik, kök 2 olur.
+5: 3'te dengesizlik, 4 yukarı çıkar: 2(1, 4(3,5)).
+6: kökte (2) dengesizlik, 4 köke çıkar, 3 alt ağacı 2'nin sağına geçer: 4(2(1,3), 5(-,6)).
+7: 5'te dengesizlik, 6 yukarı: 4(2(1,3), 6(5,7)).
Sonuç tam (perfect) ağaç, yükseklik 2. Normal BST'de aynı sıra yüksekliği 6 olan bir zincir verirdi.
Insert 60, 50, 40, 30, 20, 10 into an empty AVL tree. Give the level-order traversal of the result.
Cevabı göster
Azalan sıra, hepsi LL ve tek sağa rotasyon:
+40: 60'ta LL, kök 50: 50(40,60).
+30: dengesizlik yok. +20: 40'ta LL, 30 yukarı: 50(30(20,40),60).
+10: kökte (50) LL. 30 köke çıkar, 30'un sağ alt ağacı (40) 50'nin soluna geçer: 30(20(10), 50(40,60)).
Seviye sırası: 30 20 50 10 40 60.
Insert 40, 20, 60, 50, 70, 55 into an empty AVL tree. Which case occurs and what is the level-order traversal of the final tree?
Cevabı göster
İlk beş ekleme dengeli: 40(20, 60(50,70)).
55 eklenince yol 40 → 60 → 50 → sağ. 40'ın bf'si: 0 - 2 = -2. Yeni anahtar 40'ın sağının (60) soluna gitti: RL.
Önce 60 üzerinde sağa rotasyon (50 yukarı, 55 60'ın soluna geçer), sonra 40 üzerinde sola rotasyon (50 köke çıkar).
Sonuç: 50(40(20,-), 60(55,70)). Seviye sırası 50 40 60 20 55 70.
Insert 50, 30, 70, 20, 40, 45 into an empty AVL tree. Give the level-order traversal of the final tree.
Cevabı göster
Beş ekleme sonrası: 50(30(20,40),70). 45 → 50 → 30 → 40 → sağ.
Kökün bf'si: height(30) - height(70) = 2 - 0 = 2. 45, 50'nin solunun (30) sağına gitti: LR.
30 üzerinde sola rotasyon: 40 yukarı, 30 onun solu. Sonra 50 üzerinde sağa: 40 köke çıkar, 40'ın eski sağ çocuğu 45, 50'nin soluna geçer.
Sonuç: 40(30(20), 50(45,70)).
What is the minimum number of nodes in an AVL tree of height 5? (A single node has height 0.)
Cevabı göster
En seyrek AVL ağacında bir alt ağaç h-1, diğeri h-2 yüksekliğindedir: , , .
N(2) = 2 + 1 + 1 = 4, N(3) = 4 + 2 + 1 = 7, N(4) = 7 + 4 + 1 = 12, N(5) = 12 + 7 + 1 = 20.
Insert 5, 2, 8, 1, 4, 3 into an empty AVL tree. Where does the imbalance appear, which case is it, and what is the level-order traversal of the result?
Cevabı göster
Beş ekleme sonrası: 5(2(1,4),8), dengeli. 3 → 5 → 2 → 4 → sol.
2'nin bf'si 0 - 1 = -1 (sorun yok). 5'in bf'si 2 - 0 = 2. 3, 5'in solunun (2) sağ alt ağacına gitti: LR.
2 üzerinde sola rotasyon: 4 yukarı, 3 2'nin sağına geçer: 4(2(1,3)). Sonra 5 üzerinde sağa: 4 köke çıkar.
Sonuç: 4(2(1,3), 5(-,8)).
Insert 12, 8, 16, 4, 10, 14, 2, 6, 1 into an empty AVL tree. Which node is rebalanced and what is the level-order traversal of the final tree?
Cevabı göster
İlk sekiz ekleme dengesizlik yaratmaz: 12(8(4(2,6),10), 16(14)).
1 → 12 → 8 → 4 → 2 → sol. Yukarı doğru bf kontrolü: 2: 1, 4: 2 - 1 = 1, 8: height(4) - height(10) = 2 - 0 = 2. İlk dengesiz düğüm 8.
1, 8'in solunun solunda: LL, 8 üzerinde tek sağa rotasyon. 4 yukarı çıkar, 4'ün sağ çocuğu 6, 8'in soluna geçer: 4(2(1), 8(6,10)).
Kök 12'nin bf'si artık 2 - 1 = 1, düzeltme bitti. Sonuç seviye sırası: 12 4 16 2 8 14 1 6 10.
Write a Java method AvlNode rotateRight(AvlNode k2) that performs a single right rotation (LL case) and returns the new subtree root. Assume AvlNode has fields key, height, left, right and a helper height(t) that returns -1 for null.
Cevabı göster
Üç işaretçi değişir, sonra yükseklikler alttan yukarı güncellenir. k2 artık k1'in çocuğu olduğu için önce k2 güncellenmeli.
static AvlNode rotateRight(AvlNode k2) {
AvlNode k1 = k2.left;
k2.left = k1.right; // ortadaki alt agac (Y) k2'ye gecer
k1.right = k2;
k2.height = 1 + Math.max(height(k2.left), height(k2.right));
k1.height = 1 + Math.max(height(k1.left), height(k1.right));
return k1; // yeni kok; cagiran t = rotateRight(t) yazar
}Sık hata: yeni kökü döndürmeyi ya da çağıran tarafta t.left = rotateRight(t.left) atamasını unutmak. O zaman rotasyon ağaçtan kopar.Doğrulama: Tüm ekleme izleri, rotasyon tipleri, seviye sıraları ve N(h) değerleri Java ile üretildi (verify/SEN2212/W05.java, java W05.java). Sayfadaki AVL kodu aynı dosyada çalıştırılıp anahtar tabanlı ikinci bir uygulamayla 8 dizide karşılaştırıldı, hepsi aynı çıktı.