快乐树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。

四种情况分别对应如图:

img

LL的旋转

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

img

LL对应的旋转是左单旋转,把k1作为根节点,k2作为k1的又子树,同时将k1的右子树作为k2的左子树。

当然,旋转后要重新计算节点的高度。由于高度是自下而上的,只用取自己的子树最高高度+1即可。

代码中,以k1为最左节点,参数为最高节点(因为不能向上查,只能向下)

RR的旋转

RR对应的是右单旋转,与LL的左单旋转对称。

img

要注意的是k1永远指原左边的节点,k2是右边的节点。

LR的旋转

LR的情况通过一次旋转不能完全解决,需要两次单旋转才能恢复平衡。

img

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

RL的旋转

RL的情况和LR是对称的。

img

4. 插入

根据二叉查找树性质插入节点。递归,边界条件是到达叶子的子树(当前节点==null)

由于插入操作是不稳定的,可能改变平衡状态,需要在每层递归(但根据AVL的性质,不会超过2层不平衡)检测平衡状态,如果不平衡则进行相应的旋转。

因为进行了旋转和插入,所以递归需要在返回时重新统计高度。

5. 删除

删除相比插入更麻烦,因为删除要考虑其左右子树。简单的做法是另写两个函数(比较好写,就不写出来了)分别查找子树的最大和最小值,在左树高度更大时将左树最大值与要被删的节点互换,然后再删;在右树高度更大时将右树最小值与要被删的节点互换,直到要被删除节点的位置没有任何子树,然后再删除。这样可以保住左右子树的数据仍然符合规则的同时方便地删除节点。

完整的AVL树要能维护一组数据,肯定还要提供相应的遍历/查找等等操作,但这些都比较好写,不单独列出。

这玩意是真的难写,这个未完全版本都要150行,考场要写出来真不容易,也许有其他替代方案?

最后更新于