一、引言

AVL树是一颗自平衡的二分搜索树。它在新增、删除操作时根据平衡因子(左右子树高度差)来确定进行左旋、右旋操作,以使得可能被破坏了平衡的树得到再平衡,保证了AVL树的查询效率。

  • 具有唯一的根节点
  • 每个节点最多有两个子节点
  • 每个节点最多有一个父亲节点
  • 每个节点的值都大于左子树的值
  • 每个节点的值都小于右子树的值
  • 树中元素都具备可比较性
  • 对于任意要一个节点,左子树和右子树的高度差不能超过1

1、二分搜索树的问题

假设向二分搜索树中按顺序添加元素1、2、3、4、5、6。这种极端情况下二分搜索树会退化成链表,查询的时间复杂度退化成O(n)。AVL树弥补了这一缺陷。

2、AVL树

  • 新增节点后,破坏了树的平衡,重新计算树的高度,根据平衡因子便于下一步的旋转操作,以达到树的再平衡。

不平衡的树.png

  • 维护树的平衡

新增节点.png

说明:新增节点操作,从根节点出发,找到节点插入的合适位置,然后沿着节点向上维护平衡性,因为只有可能插入节点的祖先节点会被破坏平衡性。

  • 完全左边倾斜的树(LL)

左边倾斜的树.png

  • 右旋操作后的树

右旋操作后再平衡的树.png

  • 完全右边倾斜的树(RR)

右边倾斜的树.png

  • 左旋操作后的树

左旋操作后再平衡的树.png

  • 插入的元素在不平衡节点的左侧的右侧(LR)

首先左旋转操作.png
image.png

说明:对于LR型不平衡树,单纯地对不平衡节点进行右旋操作并不能使树达到再平衡。需要进行两次旋转操作。首先需要对x节点进行左旋转操作,此操作过后就变成了LL型不平衡树,然后再对y节点进行右旋转操作,使得树再平衡。

  • 插入的元素在不平衡节点的右侧的左侧(RL)

image.png
转化为RR型树.png

说明:对于RL型不平衡树,单纯地对不平衡节点进行左旋操作并不能使树达到再平衡。需要进行两次旋转操作。首先需要对x节点进行右旋转操作,此操作过后就变成了RR型不平衡树,然后再对y节点进行左旋转操作,使得树再平衡。

二、实现

public class AVLTree<K extends Comparable<K>, V> {

    private class Node{
        /**
         * 节点元素key
         */
        public K key;

        /**
         * 节点元素value
         */
        public V value;

        /**
         * 左右孩子节点
         */
        public Node left, right;

        /**
         * 节点高度值
         */
        public int height;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            this.left = null;
            this.right = null;
            this.height = 1;
        }
    }

    private Node root;
    private int size;

    public AVLTree(){
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 判断该二叉树是否是一颗二分搜索树
     * @return
     */
    public boolean isBST() {
        ArrayList<K> keys = new ArrayList<>();
        inOrder(root, keys);
        for (int i = 1; i < keys.size(); i++) {
            if (keys.get(i - 1).compareTo(keys.get(i)) > 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 中序遍历
     * @param node 节点
     * @param keys 保存节点元素的key
     */
    private void inOrder(Node node, ArrayList keys) {
        if (node == null) {
            return;
        }

        inOrder(node.left, keys);
        keys.add(node.key);
        inOrder(node.right, keys);
    }

    /**
     * 判断该二叉树是否是一颗平衡二叉树
     * 平衡二叉树:对于任意节点,它的左右子树的高度差不能查过1
     * @return
     */
    public boolean isBalanced() {
        return isBalanced(root);
    }

    /**
     * 判断以node为根的二叉树是否是一颗平衡二叉树,递归算法
     * @param node
     * @return
     */
    private boolean isBalanced(Node node) {
        if (node == null) {
            return true;
        }

        //平衡因子(节点左右子树高度差的绝对值)
        int balanceFactor = getBalanceFactor(node);

        if (Math.abs(balanceFactor) > 1) {
            return false;
        }

        return isBalanced(node.left) && isBalanced(node.right);
    }

    /**
     * 获取节点高度
     * @param node
     * @return
     */
    private int getHeight(Node node) {
        if (node == null) {
            return 0;
        }
        return node.height;
    }

    /**
     * 计算节点的平衡因子
     * @param node 节点
     * @return
     */
    private int getBalanceFactor(Node node) {
        if(node == null)
            return 0;
        return getHeight(node.left) - getHeight(node.right);
    }

    /**
     * 向二分搜索树中添加新的元素(key, value)
     * @param key 元素key
     * @param value 元素value
     */
    public void add(K key, V value){
        root = add(root, key, value);
    }

    /**
     * 对节点y进行向右旋转操作,返回旋转后新的根节点x
     *        y                              x
     *       / \                           /   \
     *      x   T4     向右旋转 (y)        z     y
     *     / \       - - - - - - - ->    / \   / \
     *    z   T3                       T1  T2 T3 T4
     *   / \
     * T1   T2
     * @param y 不平衡的树节点
     * @return 经过右旋操作后重新平衡的树节点
     */
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node t3 = x.right;

        //向右旋转过程
        x.right = y;
        y.left = t3;

        //更新x、y节点的高度
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;

        return x;
    }

    /**
     * 对节点y进行向左旋转操作,返回旋转后新的根节点x
     *    y                             x
     *  /  \                          /   \
     * T1   x      向左旋转 (y)       y     z
     *     / \   - - - - - - - ->   / \   / \
     *   T2  z                     T1 T2 T3 T4
     *      / \
     *     T3 T4
     * @param y 不平衡的树节点
     * @return 经过左旋操作后重新平衡的树节点
     */
    private Node leftRotate(Node y) {
        Node x = y.right;
        Node t2 = x.left;

        //向左旋转操作
        x.left = y;
        y.right = t2;

        //更新x、y节点的高度
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;

        return x;
    }

    /**
     * 向以node为根的二分搜索树中插入元素(key, value),递归算法
     * 返回插入新节点后二分搜索树的根
     * @param node 树节点
     * @param key 元素key
     * @param value 元素value
     * @return 返回插入新节点后二分搜索树的根
     */
    private Node add(Node node, K key, V value){

        if(node == null){
            size ++;
            return new Node(key, value);
        }

        if (key.compareTo(node.key) < 0) {
            node.left = add(node.left, key, value);
        } else if (key.compareTo(node.key) > 0) {
            node.right = add(node.right, key, value);
        } else {
            // key.compareTo(node.key) == 0
            node.value = value;
        }

        //更新当前节点的height
        node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));

        //计算节点的平衡因子
        int balanceFactor = getBalanceFactor(node);

//        if (Math.abs(balanceFactor) > 1) {
//            System.out.println("unbalanced : " + balanceFactor);
//        }

        //平衡维护(当前节点整体向坐倾斜)LL
        if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
            return rightRotate(node);
        }

        //平衡维护(当前节点整体向右倾斜)RR
        if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
            return leftRotate(node);
        }

        //平衡维护LR
        if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
            //LR => LL
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        //平衡维护RL
        if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
            //RL => RR
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }

    /**
     * 返回以node为根节点的二分搜索树中,key所在的节点
     * @param node 节点
     * @param key 元素key
     * @return
     */
    private Node getNode(Node node, K key){

        if(node == null)
            return null;

        if(key.equals(node.key))
            return node;
        else if(key.compareTo(node.key) < 0)
            return getNode(node.left, key);
        else // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
    }

    public boolean contains(K key){
        return getNode(root, key) != null;
    }

    public V get(K key){

        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    public void set(K key, V newValue){
        Node node = getNode(root, key);
        if(node == null)
            throw new IllegalArgumentException(key + " doesn't exist!");

        node.value = newValue;
    }

    /**
     * 返回以node为根的二分搜索树的最小值所在的节点
     * @param node
     * @return
     */
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    /**
     * 从二分搜索树中删除键为key的节点
     * @param key
     * @return
     */
    public V remove(K key){

        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    private Node remove(Node node, K key){

        if( node == null )
            return null;

        Node retNode;
        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            retNode = node;
        } else if (key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            retNode = node;
        } else {   // key.compareTo(node.key) == 0

            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                retNode = rightNode;
            } else if (node.right == null) {
                // 待删除节点右子树为空的情况
                Node leftNode = node.left;
                node.left = null;
                size--;
                retNode = leftNode;
            } else {
                // 待删除节点左右子树均不为空的情况
                // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
                // 用这个节点顶替待删除节点的位置
                Node successor = minimum(node.right);
                successor.right = remove(node.right, successor.key);
                successor.left = node.left;

                node.left = node.right = null;

                retNode = successor;
            }


        }

        //边界处理(假如删除的是叶子节点)
        if (retNode == null) {
            return null;
        }

        //更新当前节点的height
        retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));

        //计算节点的平衡因子
        int balanceFactor = getBalanceFactor(retNode);

        //平衡维护(当前节点整体向坐倾斜)LL
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) {
            return rightRotate(retNode);
        }

        //平衡维护(当前节点整体向右倾斜)RR
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) {
            return leftRotate(retNode);
        }

        //平衡维护LR
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
            //LR => LL
            retNode.left = leftRotate(retNode.left);
            return rightRotate(retNode);
        }

        //平衡维护RL
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
            //RL => RR
            retNode.right = rightRotate(retNode.right);
            return leftRotate(retNode);
        }

        return retNode;
    }
}~~~~

三、时间复杂度分析

  • AVL树再二分搜索树的基础之上进行优化,,在新增节点、删除节点操作中加入了自动平衡特性。增加了一点维护高度、平衡因子、左右旋转等计算损耗。但是大大提高了查询性能。其时间复杂度在O(logn)级别。

四、其它数据结构


neojayway
52 声望10 粉丝

学无止境,每天进步一点点