基础数据结构及算法——AVL树【自平衡二叉搜索树】解决失衡

历史

AVL 树是一种自平衡二叉搜索树,由托尔·哈斯特罗姆在 1960 年提出并在 1962 年发表。它的名字来源于发明者的名字:Adelson-Velsky 和 Landis,他们是苏联数学家,于 1962 年发表了一篇论文,详细介绍了 AVL 树的概念和性质。

概述

在二叉搜索树中,如果插入的元素按照特定的顺序排列,可能会导致树变得非常不平衡,从而降低搜索、插入和删除的效率。为了解决这个问题,AVL 树通过在每个节点中维护一个平衡因子来确保树的平衡。平衡因子是左子树的高度减去右子树的高度。如果平衡因子的绝对值大于等于 2,则通过旋转操作来重新平衡树。

AVL 树是用于存储有序数据的一种重要数据结构,它是二叉搜索树的一种改进和扩展。它不仅能够提高搜索、插入和删除操作的效率,而且还能够确保树的深度始终保持在 O(log n) 的水平。随着计算机技术的不断发展,AVL 树已经成为了许多高效算法和系统中必不可少的一种基础数据结构。

如果一棵二叉搜索树长的不平衡,那么查询的效率会受到影响,如下图

通过旋转可以让树重新变得平衡,并且不会改变二叉搜索树的性质(即左边仍然小,右边仍然大)

如何判断失衡?

如果一个节点的左右孩子,高度差超过 1,则此节点失衡,才需要旋转

失衡的四种情况

LL
  • 失衡节点(图中 8 红色)的 bf > 1,即左边更高
  • 失衡节点的左孩子(图中 6)的 bf >= 0 即左孩子这边也是左边更高或等高
LR
  • 失衡节点(图中 8)的 bf > 1,即左边更高
  • 失衡节点的左孩子(图中 3 红色)的 bf < 0 即左孩子这边是右边更高
RL
  • 失衡节点(图中 3)的 bf <-1,即右边更高
  • 失衡节点的右孩子(图中 6 红色)的 bf > 0,即右孩子这边左边更高
RR
  • 失衡节点(图中 3)的 bf <-1,即右边更高
  • 失衡节点的右孩子(图中 5 黄色)的 bf <= 0,即右孩子这边右边更高或等高

解决失衡

java 复制代码
public class AVLTree<K extends Comparable<K>, V> {
    private AVLNode<K, V> root;

    //左旋,把要旋转的节点 -> 新的根节点
    private AVLNode<K, V> leftRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 2:
         *      2                   4
         *     / \                 / \
         *    1   4         ->    2   5
         *       / \             / \   \
         *      3   5           1  3    6
         *           \
         *            6
         */
        AVLNode<K, V> right = node.right;       //4节点
        AVLNode<K, V> rightLeft = right.left;   //3节点
        right.left = node;                      //设置4节点的左子树为2节点
        node.right = rightLeft;                 //设置2节点的右子树为3节点
        updateHeight(node);                     //更新树的高度
        updateHeight(right);                    //更新树的高度
        return right;
    }

    //右旋,把要旋转的节点 -> 新的根节点
    private AVLNode<K, V> rightRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 8:
         *       8              6
         *      / \            / \
         *     6   9    ->    5   8
         *    / \                / \
         *   5  7               7  9
         */
        AVLNode<K, V> left = node.left;         //6节点
        AVLNode<K, V> leftRight = left.right;   //7节点
        left.right = node;                      //设置6节点的右子树为8节点
        node.left = leftRight;                  //设置8节点的左子树为7节点
        updateHeight(node);                     //更新树的高度
        updateHeight(left);                     //更新树的高度
        return left;
    }

    //先左旋再右旋
    private AVLNode<K, V> leftRightRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 6:
         *          6                           6                       4
         *         / \                         / \                     / \
         *        2   7                       4   7                   2   6
         *       / \           左旋后->       / \        右旋后->    / \  / \
         *      1   4                       2  5                   1  3  5  7
         *         / \                     / \
         *        3   5                   1   3
         */
        node.left = leftRotate(node.left);//节点2进行左旋,赋值给6的左子树
        return rightRotate(node);//节点6右旋
    }

    //先右旋再左旋
    private AVLNode<K, V> rightLeftRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 6:
         *          2                           2                           4
         *         / \                         / \                         / \
         *        1   6                       1   4                       2   6
         *           / \         右旋后->         / \        左旋后->    / \  / \
         *          4   7                      3    6                  1  3  5  7
         *         / \                             / \
         *        3   5                           5   7
         */
        node.right = rightRotate(node.right);
        return leftRotate(node);
    }

    //获取节点的高度
    private int height(AVLNode<K, V> node) {
        return node == null ? 0 : node.height;
    }

    //根据节点高度(新增,删除,旋转)
    private void updateHeight(AVLNode<K, V> node) {
        node.height = Integer.max(height(node.left), height(node.right)) + 1;
    }

    //平衡因子,左子树的高度 - 右子树的高度
    private int balanceFactor(AVLNode<K, V> node) {
        return height(node.left) - height(node.right);
    }

    //检查节点是否是平衡的二叉树,如果不是就旋转
    private AVLNode<K, V> balance(AVLNode<K, V> node) {
        if (node == null) {
            return null;
        }
        int height = balanceFactor(node);//如果返回0,-1,1表示是平衡的,否则不是
        if (height > 1 && balanceFactor(node.left) >= 0) {//右旋,要考虑=0的情况
            return rightRotate(node);
        } else if (height > 1 && balanceFactor(node.left) < 0) {//先左旋后右旋
            return leftRightRotate(node);
        } else if (height < -1 && balanceFactor(node.right) <= 0) {//左旋,要考虑=0的情况
            return leftRotate(node);
        } else if (height < -1 && balanceFactor(node.right) > 0) {//先右旋后左旋
            return rightLeftRotate(node);
        }
        return node;
    }

    //添加
    public void put(K key, V value) {
        root = doPut(root, key, value);
    }

    private AVLNode<K, V> doPut(AVLNode<K, V> node, K key, V value) {
        //1.找到空位, 创建新节点
        if (node == null) {
            return new AVLNode<>(key, value);
        }
        //2.key 已存在, 更新
        if (key.compareTo(node.key) == 0) {
            node.value = value;
            return node;
        }
        //3.继续查找
        if (key.compareTo(node.key) < 0) {
            node.left = doPut(node.left, key, value);
        } else {
            node.right = doPut(node.right, key, value);
        }
        updateHeight(node);
        return balance(node);
    }

    //删除
    public void remove(K key) {
        root = doRemove(root, key);
    }

    //递归执行,返回删除后的节点
    private AVLNode<K, V> doRemove(AVLNode<K, V> node, K key) {
        //1.node == null
        if (node == null) {
            return null;
        }
        //2.没有找到key
        if (key.compareTo(node.key) > 0) {
            node.right = doRemove(node.right, key);
        } else if (key.compareTo(node.key) < 0) {
            node.left = doRemove(node.left, key);
        } else {
            /**
             * 找到key,删除操作分为四种情况
             * 1.删除节点没有左子树,将右子树托孤给parent
             * 2.删除节点没有右子树,将左子树托孤给parent
             * 3.删除节点没有左右子树,将null托孤给parent
             * 4.删除节点有左右子树,将后继节点托孤给parent
             *      1.找删除节点的后继节点
             *      2.处理后继节点
             *      3.后继节点取代删除的节点
             */
            if (node.left == null && node.right == null) {
                return null;
            } else if (node.left == null) {
                node = node.right;
            } else if (node.right == null) {
                node = node.left;
            } else {
                AVLNode<K, V> p = node.right;
                while (p.left != null) {
                    p = p.left;
                }
                p.right = doRemove(node.right, p.key);
                p.left = node.left;
                node = p;
            }
        }
        //4.更新高度
        updateHeight(node);
        //5.平衡节点
        return balance(node);
    }

    //查询
    public V get(K key) {
        AVLNode<K, V> pointer = root;
        while (pointer != null) {
            K k = pointer.key;
            if (key.compareTo(k) > 0) {
                pointer = pointer.right;
            } else if (key.compareTo(k) < 0) {
                pointer = pointer.left;
            } else {
                return pointer.value;
            }
        }
        return null;
    }

    private static class AVLNode<K, V> {
        private int height = 1;//节点的高度,默认是1
        private K key;
        private V value;
        private AVLNode<K, V> left;
        private AVLNode<K, V> right;

        public AVLNode(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
}
相关推荐
XiaoLeisj9 分钟前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
Jasmine_llq28 分钟前
《 火星人 》
算法·青少年编程·c#
闻缺陷则喜何志丹39 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
Lenyiin1 小时前
01.02、判定是否互为字符重排
算法·leetcode
鸽鸽程序猿1 小时前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列
Jackey_Song_Odd1 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v1 小时前
leetCode43.字符串相乘
java·数据结构·算法
A懿轩A2 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组