数据结构学习笔记–BST与AVL
前言
BST和AVL复习。
BST
二叉排序树的3个主要性质:
- 若左子树不空,则左子树上的所有节点的值均小于根节点的值。
- 若右子树不空,则右子树上的所有节点的值均大于根节点的值。
- 左、右子树也分别为二叉排序树。
对于插入操作,按照上述规则,递归即可。
但是对于删除操作,涉及到排序规则的保持,可分3中情况考虑:
- 若删除节点为叶子节点,则删除该节点不影响整棵树的结果,只需修改父节点指针即可。
- 若删除节点只有左子树或只有右子树,此时,只要令左子树或右子树替换删除节点即可。
- 若左子树与右子树均不为空,则使用删除节点的直接后继节点的值代替删除节点的值,然后删除直接后继节点即可。
严蔚敏版的数据结构,对于第3中情况,有不同两种处理方式:
- 使删除节点的左子树代替删除节点,使删除节点的右子树成为删除节点直接前驱的右子树。
或 - 使用删除节点的直接前驱的值代替删除节点的值,然后删除直接前驱节点。
其中第2种方式与上面第3中情况的处理方式类似,容易理解,因为要保持顺序性,要么直接前驱、要么直接后继来代替,才能保持有序; 第1种方式稍微有点绕,不过删除节点右子树成为直接前驱的右子树,也是保持着有序性的。
不过从代码实现的简洁程度和便于理解的角度,我倾向于使用上面第3种情况的处理方式。
AVL
BST在插入关键字有序时,会蜕化为单支树,所以引出了平衡二叉树。平衡二叉树除了具有二叉搜索树的性质外,还有两个性质:
- 左子树和右子树都是平衡二叉树。
- 左子树和右子树高度只差的绝对值不超过1。
节点高度的定义为从该节点到叶子节点的路径(边)的长度,叶子节点的高度为0。
从多出来的两个性质可以看出,变化主要在插入和删除操作上,对于这两个操作,除了要保持顺序性,现在还要保持平衡。
假设在节点v上出现了不平衡:
bf = height(v.left) - height(v.right)
bf2 = height(v.left.left) - height(v.left.right)
bf3 = height(v.right.left) - height(v.right.right)
则有四种不平衡的场景:
- bf=2,bf2=1
- bf=2, bf2=-1
- bf=-2, bf3=1
- bf=-2, bf3=-1
场景梳理出来,就有对应的解决方法:
- 单向右旋。 (对应场景1。)
- 单向左旋。 (对应场景4。)
- 先右旋,后左旋。 (对应场景3)
- 先左旋,后右旋。 (对应场景2)
另一种方式是使用平衡因子,也就是上面算的bf来作为节点属性进行平衡操作,具体参见严蔚敏版的数据结果。 不过,同样,从代码实现和便于理解的角度来看,我倾向于使用高度来作为节点属性。
总结
BST和AVL原理简单清晰,但是真要自己动手实现起来,还是很麻烦的,尤其是想把代码写的简洁。 惯例吐槽,严蔚敏版的数据结构,读起来确实累。
附录:
- 《数据结构(C语言版)》 – 严蔚敏
- 《算法导论》
- 算法可视化网站–visualgo
附上自己的实现(JAVA版)
代码参考了VISUALGO的实现,没有注释,结合上面的描述对应着看吧。
/**
* avl tree
* @author lhz
*/
public class AvlTree extends BinarySearchTree {
public void insert(Comparable v) {
if (v == null) {
throw new NullPointerException();
}
root = insert(root, v);
}
private int height(Node v) {
return v == null ? -1 : v.height;
}
private Node rotateRight(Node v) {
Node lc = v.left;
v.left = lc.right;
if (lc.right != null) {
lc.right.parent = v;
}
lc.right = v;
lc.parent = v.parent;
v.parent = lc;
v.height = Math.max(height(v.left), height(v.right)) + 1;
lc.height = Math.max(height(lc.left), height(lc.right)) + 1;
return lc;
}
private Node rotateLeft(Node v) {
Node rc = v.right;
v.right = rc.left;
if (rc.left != null) {
rc.left.parent = v;
}
rc.parent = v.parent;
rc.left = v;
v.parent = rc;
v.height = Math.max(height(v.left), height(v.right)) + 1;
rc.height = Math.max(height(rc.left), height(rc.right)) + 1;
return rc;
}
@Override
protected Node insert(Node v, Comparable k) {
v = super.insert(v, k);
v = balance(v);
return v;
}
public void remove(Comparable v) {
root = remove(root, v);
}
@Override
protected Node remove(Node v, Comparable k) {
v = super.remove(v, k);
v = balance(v);
return v;
}
protected Node balance(Node k) {
if (k == null) {
return null;
}
int balance = height(k.left) - height(k.right);
if (balance == 2) {
int balance2 = height(k.left.left) - height(k.left.right);
if (balance2 == 1) {
k = rotateRight(k);
} else {
k.left = rotateLeft(k.left);
k = rotateRight(k);
}
} else if (balance == - 2) {
int balance2 = height(k.right.left) - height(k.right.right);
if (balance2 == -1) {
k = rotateLeft(k);
} else {
k.right = rotateRight(k.right);
k = rotateLeft(k);
}
}
k.height = Math.max(height(k.left), height(k.right)) + 1;
return k;
}
}
/**
* avl tree
* @author lhz
*/
public class BinarySearchTree {
class Node {
public Comparable value;
public int bf; //balance factor
public int height;
public int ref;//same value reference count
public Node left;
public Node right;
public Node parent;
public Node(Comparable v) {
value = v;
}
public String toString() {
return String.format("[%s:%d, h=%d, bf=%d]", value, ref, height, bf);
}
}
protected Node root;
public Comparable min(Node v) {
if (v == null) {
return null;
}
while (v.left != null) {
v = v.left;
}
return v.value;
}
public Comparable max(Node v) {
if (v == null) {
return null;
}
while (v.right != null) {
v = v.right;
}
return v.value;
}
public Node search(Node v, Comparable k) {
if (v == null) {
return null;
}
int compared = k.compareTo(v.value);
if (compared > 0) {
return search(v.right, k);
} else if (compared < 0) {
return search(v.left, k);
} else {
return v;
}
}
protected Node insert(Node v, Comparable k) {
if (v == null) {
return new Node(k);
}
int compared = k.compareTo(v.value);
if (compared > 0) {
v.right = insert(v.right, k);
v.right.parent = v;
} else if (compared < 0) {
v.left = insert(v.left, k);
v.left.parent = v;
} else {
v.ref++;
}
return v;
}
protected Node remove(Node v, Comparable k) {
if (v == null) {
return null;
}
int compared = k.compareTo(v.value);
if (compared > 0) {
v.right = remove(v.right, k);
if (v.right != null) v.right.parent = v;
} else if (compared < 0) {
v.left = remove(v.left, k);
if (v.left != null) v.left.parent = v;
} else {
if (v.right == null && v.left == null) {
return null;
} else if (v.left != null && v.right != null) {
Comparable s = successor(v);
v.value = s;
v.right = remove(v.right, s);
} else if (v.left != null) {
return v.left;
} else if (v.right != null) {
return v.right;
}
}
return v;
}
protected Comparable successor(Node v) {
if (v.right != null) {
return min(v.right);
} else {
Node p = v.parent;
Node c = v;
while (p != null && p.right == c) {
c = p;
p = p.parent;
}
return p == null ? null : p.value;
}
}
protected Comparable predecessor(Node v) {
if (v.left != null) {
return max(v.left);
} else {
Node p = v.parent;
Node c = v;
while (p != null && p.left == c) {
c = p;
p = p.parent;
}
return p == null ? null : p.value;
}
}
}