快乐树0x01:AVL树的java实现
写几个码量比较大的数据结构试试看,不知道过几天还记不记的得
1. AVL树
树的高度:根节点到叶子节点的最长距离
AVL树是一种平衡二叉树,其任何一个节点的两个子树的高度最大差为1
2. 节点
节点定义
public class AVLTree<T extends Comparable<T>>{
//用java自带的泛型,实现Comparable接口做比较
Node<T> root;//跟节点
class Node<T extends Comparable<T>>{
T value;
Node<T> left;
Node<T> right;
int height; //节点的高度
public Node(T key, Node<T> left, Node<T> right){
this.value = key;
this.left = left;
this.right = right;
this.height = 0;
}
}
}相比普通的二叉树,会记录多一个节点的高度。
取height函数
3. 旋转
旋转操作是AVL树平衡的核心。旋转操作其实就是一种改变根节点,从而使得树平衡的方法。
如果在AVL树中进行插入或删除操作,可能会导致平衡被打破。
可以分为4种情况:LL、LR、RL和RR。
四种情况分别对应如图:

LL的旋转
LL情况下,可进行一次单旋转:

LL对应的旋转是左单旋转,把k1作为根节点,k2作为k1的又子树,同时将k1的右子树作为k2的左子树。
当然,旋转后要重新计算节点的高度。由于高度是自下而上的,只用取自己的子树最高高度+1即可。
代码中,以k1为最左节点,参数为最高节点(因为不能向上查,只能向下)
RR的旋转
RR对应的是右单旋转,与LL的左单旋转对称。

要注意的是k1永远指原左边的节点,k2是右边的节点。
LR的旋转
LR的情况通过一次旋转不能完全解决,需要两次单旋转才能恢复平衡。

对LR的双旋转其实就是 围绕k1进行一次右单旋转和 围绕k3进行一次左单旋转。
RL的旋转
RL的情况和LR是对称的。

4. 插入
根据二叉查找树性质插入节点。递归,边界条件是到达叶子的子树(当前节点==null)
由于插入操作是不稳定的,可能改变平衡状态,需要在每层递归(但根据AVL的性质,不会超过2层不平衡)检测平衡状态,如果不平衡则进行相应的旋转。
因为进行了旋转和插入,所以递归需要在返回时重新统计高度。
5. 删除
删除相比插入更麻烦,因为删除要考虑其左右子树。简单的做法是另写两个函数(比较好写,就不写出来了)分别查找子树的最大和最小值,在左树高度更大时将左树最大值与要被删的节点互换,然后再删;在右树高度更大时将右树最小值与要被删的节点互换,直到要被删除节点的位置没有任何子树,然后再删除。这样可以保住左右子树的数据仍然符合规则的同时方便地删除节点。
完整的AVL树要能维护一组数据,肯定还要提供相应的遍历/查找等等操作,但这些都比较好写,不单独列出。
这玩意是真的难写,这个未完全版本都要150行,考场要写出来真不容易,也许有其他替代方案?
最后更新于