前一篇学习了二叉搜索树,本篇试图学习 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