前一篇學習了二叉搜索樹,本篇試圖學習 AVL 樹。惋惜的是,Mehta 教授的《數據結構基礎》一書中沒有給出
刪除算法,我還沒研究清楚,只好先不寫(學)刪除部分了。java
修改後的 TestTree.java 文件在這裏: http://vdisk.weibo.com/s/3fAzF算法
如下是 insert 部分的代碼,註釋我已經寫得挺清楚的了:數據結構
public void insert(K key, V value) { this.checkValid(); // 檢查如今樹的有效性。 internal_insert(key, value); // 實際執行插入。 this.checkValid(); // 插入以後再檢查一次。 } /** 內部實現 insert, 返回插入或更新的節點對象。 */ private boolean internal_insert(K key, V value) { if (this.root == null) { // 特定狀況,簡單處理。 this.root = new TreeNode<K,V>(key, value); return true; } // 第一步:查找 key 的插入位置。 boolean found = false; TreeNode<K, V> a = root, // 離插入點最近的 bf=+-1 的節點,也可能爲 root(其 bf 可能爲 0)。 f = null, // a 的父節點,可能爲 null。 p = root, // p 用於根據 key 訪問節點樹。 q = null, // q 是 p 的父節點,可能爲 null。 y = null; // y 是新插入的節點,或原來已經在樹中的節點。 while (p != null && !found) { // p 遍歷從 root -> key 節點的整個路徑。 if (p.bf != 0) { a = p; // 記錄 a 爲最接近的那個 bf=+-1 的節點。 f = q; } int comp = key.compareTo(p.key); if (comp < 0) { q = p; // 若是 key < p.key 則查找左子樹,q = p.parent。 p = p.left; } else if (comp > 0) { q = p; // 若是 key > p.key 則查找右子樹。 p = p.right; } else { // key == p.key,則 y 已經在樹中了。 y = p; found = true; } } // end of while: 查找插入位置。 if (found) { y.setValue(value); return false; } // 第二步:插入新節點並從新平衡樹。如今 key 不在樹中,並應做爲 pp 的某個 // 子節點插入到樹中。 assert(q != null); // 必定非空,因至少有 root 節點。 assert(key.compareTo(q.key) != 0); // key != pp.key y = new TreeNode<K, V>(key, value); // 要插入的新節點。 if (key.compareTo(q.key) < 0) q.left = y; // key < pp.key,做爲左子樹插入到 pp 中。 else q.right = y; // key > pp.key,做爲右子樹插入到 pp 中。 // 調整從節點 a->pp 路徑上的節點的平衡因子 bf(balance factor) int d; // d = +-1 表示要調整的 bf 的大小。+1 意味着插入到了左子樹,-1 意味着插入到了右子樹。 TreeNode<K, V> b, // a 的子節點,多是 Al|Ar。 c; // b 的子節點,多是 Bl|Br。 if (key.compareTo(a.key) > 0) { // (k > a.key) b = p = a.right; // 插入到了 a 的右邊,則設置 b,p 是 a 的右子樹。 d = -1; // d=-1 意味着插入到了右子樹。 } else { b = p = a.left; // 插入到了 a 的左邊,則設置 b,p 是 a 的左子樹。 d = +1; // d=+1 意味着插入到了左子樹。 } // 遍歷從 p=a.child -> y 的節點路徑,這些節點的 bf=0,根據插入節點的方向調整 bf 值。 while (p != y) { assert(p.bf == 0); if (key.compareTo(p.key) > 0) { p.bf = -1; // 被插入到了右邊,則 bf 右傾斜 -1 p = p.right; } else { p.bf = +1; // 被插入到了左邊,則 bf 左傾斜 +1 p = p.left; } } // end of while (p != y); // a 節點原來 a.bf=+-1,若是更改了則還平衡嗎? if (is_balance(a.bf + d)) { // is_balance 爲 Math.abs(a.bf+d) <= 1 a.bf += d; return true; } // 新的 a.bf = 2 或者 -2 assert((a.bf + d) == 2 || (a.bf + d) == -2); if (d == 1) { // 插入到左子樹,則 a.bf = 2 (必左傾斜) assert(a.bf + d == 2); if (b.bf == 1) { // b 是 a 的子節點,則插入到了 B.left 方向。 // 執行一次 LL 旋轉。A.right 不變; A.left=原B.right; B.left 不變; B.right=A // 旋轉以後,A.bf=0,因 h(Al)==h(Ar);B.bf=0,因h(Bl)=h(Br)=h(A) a.left = b.right; b.right = a; a.bf = 0; b.bf = 0; } else { // 插入到了 B.right 右子樹,則 b.bf = -1 assert(b.bf == -1); // 執行一次 LR 旋轉(至關於 RR+LL 旋轉)。參見書上的 圖10.13。 c = b.right; b.right = c.left; a.left = c.right; c.left = b; c.right = a; // 根據原來的 c.bf 值(可能值是 -1,0,1) 分別計算新的 a,b,c 的 bf 值。 if (c.bf == 1) { // h(Cl)=h, h(Cr)=h-1,則 h(new_B)=0, h(new_A)=-1 b.bf = 0; a.bf = -1; } else if (c.bf == -1) { // h(Cl)=h-1, h(Cr)=h,則 h(new_B)=1, h(new_A)=0 b.bf = 1; a.bf = 0; } else if (c.bf == 0) { // h(Cl)==h(Cr)==h,則 h(new_B)=0, h(new_A)=0 b.bf = 0; a.bf = 0; } c.bf = 0; b = c; // b 如今指向新的根(替換了原來的 a) } // end of if } else { assert(d == -1); assert(a.bf + d == -2); // 插入到了右子樹,則 a.bf = -2(必右傾斜) if (b.bf == -1) { // 插入到了 B 的右子樹,則執行 RR 旋轉。 a.right = b.left; b.left = a; a.bf = 0; b.bf = 0; // 旋轉以後,h(Al)=h(new_ar=old_Bl)=h, h(new_Bl=A)=h(Br)=h+1 } else { // 要執行 RL 旋轉。 assert(b.bf == 1); c = b.left; // 書上沒有圖了,須要本身畫圖。 a.right = c.left; b.left = c.right; c.left = a; c.right = b; // 根據 c.bf 值(可能爲 -1,0,1),分別計算新的 a,b,c 的 bf 值。 if (c.bf == 1) { a.bf = 0; b.bf = -1; // bf(A)=h(Al)-h(Cl)=h-h=0; bf(B)=h(Cr)-h(Br)=(h-1)-h=-1 } else if (c.bf == -1) { a.bf = 1; b.bf = 0; // bf(A)=h(Al)-h(Cl)=h-(h-1)=1; bf(B)=h(Cr)-h(Br)=h-h=0 } else if (c.bf == 0) { a.bf = 0; b.bf = 0; // bf(A)=h-h=0; bf(B)=h-h=0 } c.bf = 0; b = c; // 同 LR 中。 } } // f 是 a 的父節點,若是 a 是 root 則 f == null。經旋轉以後 b(或c) 如今替代了 a 。 if (f == null) this.root = b; else if (a == f.left) f.left = b; else if (a == f.right) f.right = b; return true; }
爲方便補上 RL 旋轉部分,畫圖以下:學習
比較費事的地方是根據 bf(C)=-1,0,1 三種狀況分別要計算旋轉後的 bf(A), bf(B) 的值。用圖能夠方便地
標記旋轉後的 CL, CR 的高度(通常爲 h 或 h-1),而後計算出 bf(A), bf(B) 的值。 this