前一篇学习了二叉搜索树,本篇试图学习 AVL 树。可惜的是,Mehta 教授的《数据结构基础》一书中没有给出
删除算法,我还没研究清楚,只好先不写(学)删除部分了。
修改后的 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) 的值。
来源:oschina
链接:https://my.oschina.net/u/232554/blog/49552