AVL树

AVL树

最早的平衡二叉树,在二分搜索树的基础上做了自平衡处理,保证不会退化成链表。

二叉树的分类

  • 满二叉树:除了叶子节点,其他全部节点都有两个孩子节点。

  • 完全二叉树:节点都是从左到右插入进来的,最后一层一定是叶子节点,不满的部分一定是在右边部分。简单来说,就是把元素排列成树的形状,元素从左到右,一层层的插入进来,那么元素不够的时候,缺失的部分一定是在最后一层的右边。叶子节点一定在倒数第一或第二层。

  • 平衡二叉树:对于任意一个节点,左子树和右子树的高度差不能超过1。

平衡因子

平衡因子表示左子树跟右子树的高度差,因此每个节点也就引入了高度值,叶子节点的高度值默认设为1,父节点的高度为左右子树高度值的最大值再加1。

平衡维护时机

LL 和 RR 情况

当插入的元素在不平衡节点的左侧的左侧时,会导致平衡因子大于1的情况(即平衡被打破),这种情况称之为 LL 的情况。同理,当插入的元素在不平衡节点的右侧的右侧时,平衡也会被打破,这种情况称之为 RR 的情况。

LL 的图示如下(RR情况同理):

LR 和 RL 情况

当插入的元素在不平衡节点的左侧的右侧时,会导致平衡因子大于1的情况(即平衡被打破),这种情况称之为 LR 的情况。同理,当插入的元素在不平衡节点的右侧的左侧时,平衡也会被打破,这种情况称之为 RL 的情况。

LR 的图示如下(RL情况同理):

平衡维护方式

LL 情况

需要对打破平衡的节点进行右旋转操作,如下图所示, y 节点打破了平衡,我们对 y 进行一次右旋转操作,操作如图示:

RR 情况

需要对打破平衡的节点进行左旋转操作,如下图所示, y 节点打破了平衡,我们对 y 进行一次左旋转操作,操作如图示一样:

左旋转前图示如下:

左旋转后图示如下:

LR 情况

如下图所示,y 节点打破了平衡,我们需要对打破平衡的节点的左节点 x 进行左旋转操作,然后就转化成了 LL 的情况,再按 LL 情况处理即可:

RL 情况

如下图所示,y 节点打破了平衡,我们需要对打破平衡的节点的右节点 x 进行右旋转操作,然后就转化成了 RR 的情况,再按 RR 情况处理即可:

AVL树代码

public class AVLTree<K extends Comparable<K>, V> {
    private Node root;
    private int size;

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

    /**
     * 获取节点对应的平衡因子,即左右子树的高度差
     */
    private int getBalanceFactor(Node node) {
        if (node == null) {
            return 0;
        } else {
            return getHeight(node.left) - getHeight(node.right);
        }
    }

    /**
     * 判断该二叉树是否是一棵二分搜索树
     */
    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;
    }

    /**
     * 判断该二叉树是否是一棵平衡二叉树
     */
    public boolean isBalanced() {
        return isBalanced(root);
    }

    /**
     * 判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法
     */
    private boolean isBalanced(Node node) {
        if (node == null) {
            return true;
        }
        if (Math.abs(getBalanceFactor(node)) > 1) {
            return false;
        }
        return isBalanced(node.left) && isBalanced(node.right);
    }

    /**
     * 右旋转
     */
    private Node rightRorate(Node node) {
        Node retNode = node.left;
        Node x = retNode.right;
        node.left = x;
        retNode.right = node;

        // 更新height,一定要先更新 node 的,后更新 retNode
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        retNode.height = Math.max(getHeight(retNode.left), getHeight(retNode.right)) + 1;

        return retNode;
    }

    /**
     * 右旋转
     */
    private Node leftRorate(Node node) {
        Node retNode = node.right;
        Node x = retNode.left;
        node.right = x;
        retNode.left = node;

        // 更新height,一定要先更新 node 的,后更新 retNode
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        retNode.height = Math.max(getHeight(retNode.left), getHeight(retNode.right)) + 1;

        return retNode;
    }

    private void inOrder(Node node, ArrayList<K> keys) {
        if (node == null) {
            return;
        }
        inOrder(node.left, keys);
        keys.add(node.key);
        inOrder(node.right, keys);
    }

    // 向AVL树中添加新的元素(key, value)
    public void add(K key, V value) {
        root = add(root, key, value);
    }

    /**
     * 向以node为根的AVL树中插入元素(key, value),递归算法
     * 返回插入新节点后AVL树的根
     */
    private Node add(Node node, K key, V value) {
        if (node == null) {
            size++;
            return new Node(key, value);
        }
        if (node.key.compareTo(key) < 0) {
            node.right = add(node.right, key, value);
        } else if (node.key.compareTo(key) > 0) {
            node.left = add(node.left, key, value);
        } else {
            node.value = value;
        }

        // 更新 height
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;

        // 平衡维护
        if (getBalanceFactor(node) > 1 && getBalanceFactor(node.left) >= 0) {
            //LL情况,需要右旋转
            return rightRorate(node);
        }

        if (getBalanceFactor(node) > 1 && getBalanceFactor(node.left) < 0) {
            //LR情况,需要左旋转,然后右旋转
            node.left = leftRorate(node.left);
            return rightRorate(node);
        }

        if (getBalanceFactor(node) < -1 && getBalanceFactor(node.right) <= 0) {
            //RR情况,需要左旋转
            return leftRorate(node);
        }

        if (getBalanceFactor(node) < -1 && getBalanceFactor(node.right) > 0) {
            //RL情况,需要右旋转,然后左旋转
            node.right = rightRorate(node.right);
            return leftRorate(node);
        }

        return node;
    }

    /**
     * 从AVL树中删除键为key的节点
     */
    public V remove(K key) {
        Node node = getNode(root, key);
        if (node == null) {
            return null;
        } else {
            root = remove(root, key);
            return node.value;
        }
    }

    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 {
            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);
                //注意这里,不能用removeMin方法,因为会打破平衡性(也可以在removeMin方法里进行平衡维护)
                successor.right = remove(node.right, successor.key);
                successor.left = node.left;
                node.left = node.right = null;
                retNode = successor;
            }
        }

        //注意删除的是叶子节点时,retNode可能为null
        if (retNode == null) {
            return null;
        }

        // 更新 height
        retNode.height = Math.max(getHeight(retNode.left), getHeight(retNode.right)) + 1;

        // 平衡维护
        if (getBalanceFactor(retNode) > 1 && getBalanceFactor(retNode.left) >= 0) {
            //LL情况,需要右旋转
            return rightRorate(retNode);
        }

        //疑惑,为什么不能等于0
        if (getBalanceFactor(retNode) > 1 && getBalanceFactor(retNode.left) < 0) {
            //LR情况,需要左旋转,然后右旋转
            node.left = leftRorate(retNode.left);
            return rightRorate(retNode);
        }

        if (getBalanceFactor(retNode) < -1 && getBalanceFactor(retNode.right) <= 0) {
            //RR情况,需要左旋转
            return leftRorate(retNode);
        }

        //疑惑,为什么不能等于0
        if (getBalanceFactor(retNode) < -1 && getBalanceFactor(retNode.right) > 0) {
            //RL情况,需要右旋转,然后左旋转
            retNode.right = rightRorate(retNode.right);
            return leftRorate(retNode);
        }

        return retNode;
    }

    /**
     * 向以 node 为根的树中找最小元素并返回
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 向以 node 为根的树中寻找最大元素并返回
     */
    private Node maximum(Node node) {
        if (node.right == null) {
            return node;
        }
        return maximum(node.right);
    }

    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;
    }

    /**
     * 向以 node 为根的树中查询key对应的结点,递归算法
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else if (key.compareTo(node.key) > 0) {
            return getNode(node.right, key);
        } else {
            return node;
        }
    }

    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            throw new IllegalArgumentException(key + " does not exist!");
        } else {
            node.value = newValue;
        }
    }

    public int getSize() {
        return size;
    }

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

    private class Node {
        public K key;
        public V value;
        public Node left, right;
        public int height;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            // 节点高度值默认为1
            height = 1;
        }
    }
}

BST 和 AVLTree 的性能比较

BST 在元素按排序的方式插入时,会退化成链表,因为 BST 没有自平衡机制 。

public class AVLMain {

    public static void main(String[] args) {
        ArrayList<String> words = new ArrayList<>();
        if (FileOperation.readFile("F:\\java_projects\\data_structure\\DataStructures\\src\\a-tale-of-two-cities.txt", words)) {
            System.out.println("Total words: " + words.size());

            // 排序之后,二分搜索树会退化成链表
            Collections.sort(words);

            // Test BST
            long startTime = System.nanoTime();

            BSTMap<String, Integer> bst = new BSTMap<>();
            for (String word : words) {
                if (bst.contains(word))
                    bst.set(word, bst.get(word) + 1);
                else
                    bst.add(word, 1);
            }

            for(String word: words)
                bst.contains(word);

            long endTime = System.nanoTime();

            double time = (endTime - startTime) / 1000000000.0;
            System.out.println("BST: " + time + " s");


            // Test AVL Tree
             startTime = System.nanoTime();

            AVLTree<String, Integer> avl = new AVLTree<>();
            for (String word : words) {
                if (avl.contains(word))
                    avl.set(word, avl.get(word) + 1);
                else
                    avl.add(word, 1);
            }

            for (String word : words)
                avl.contains(word);

             endTime = System.nanoTime();

             time = (endTime - startTime) / 1000000000.0;
            System.out.println("AVL: " + time + " s");
        }

        System.out.println();
    }
}

当 BST 退化成链表时,运行结果如下:

Total words: 141489
BST: 43.441510112 s
AVL: 0.112137956 s

当 BST 不退化链表时,运行结果如下:

Total words: 141489
BST: 0.305946419 s
AVL: 0.285817112 s

总结: BST 不退化成链表时,性能跟 AVLTree 相差不大,因为元素是随机进入时,两者的时间复杂度均为 O(logn),当退化成链表时,BST 的复杂度会变成 O(n) ,此时性能相差较大。

底层为 AVLTree 的映射

我们可以对比基于 BST 实现的映射和基于 AVLTree 实现的映射的性能,总体来说 AVLTree 更优。

public class AVLMap<K extends Comparable<K>,V> implements Map<K,V> {

    private AVLTree<K, V> tree;

    public AVLMap() {
        tree = new AVLTree<>();
    }

    @Override
    public void add(K key, V value) {
        tree.add(key,value);
    }

    @Override
    public V remove(K key) {
        return tree.remove(key);
    }

    @Override
    public boolean contains(K key) {
        return tree.contains(key);
    }

    @Override
    public V get(K key) {
        return tree.get(key);
    }

    @Override
    public void set(K key, V newValue) {
        tree.set(key,newValue);
    }

    @Override
    public int getSize() {
        return tree.getSize();
    }

    @Override
    public boolean isEmpty() {
        return tree.isEmpty();
    }

}

底层为 AVLTree 的集合

我们可以对比基于 BST 实现的集合和基于 AVLTree 实现的集合的性能,总体来说 AVLTree 更优。

public class AVLSet<E extends Comparable<E>> implements Set<E> {

    private AVLTree<E, Object> avlTree;

    public AVLSet() {
        avlTree = new AVLTree<>();
    }

    @Override
    public void add(E e) {
        avlTree.add(e,null);
    }

    @Override
    public void remove(E e) {
        avlTree.remove(e);
    }

    @Override
    public boolean contains(E e) {
        return avlTree.contains(e);
    }

    @Override
    public int getSize() {
        return avlTree.getSize();
    }

    @Override
    public boolean isEmpty() {
        return avlTree.isEmpty();
    }
}