C++ 数据结构:AVL树原理与实现

一. AVL树的概念

1.1 为什么需要 AVL 树

二叉搜索树具有良好的查找效率,其平均复杂度为O(log N),但是其有一个致命问题:如果数据是有序插入,BST会退化为线性结构,如下图所示

时间复杂度可能退化为 O(N)。为解决这一问题,人们提出了自平衡二叉搜索树。其中最早的一种实现是 AVL 树,由 Adelson-VelskyLandis在 1962 年发明,其核心思想是

通过控制左右子树高度差,使树始终保持平衡


1.2 AVL 树的定义

AVL 树需要满足以下两个特性:

  1. 必须是一棵二叉搜索树
  2. 每个节点的左右子树高度差绝对值不超过 1

因此AVL 树又称高度平衡二叉搜索树。为了保持这种平衡特性,需要引入一个关键概念:平衡因子(Balance Factor)

定义:bf = 右子树高度 - 左子树高度。每个节点的平衡因子必须满足**bf ∈ { -1, 0, 1 }**这一条件

根据上述条件,AVL 树的结构可以这样设计

cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{
    pair<K, V> _kv;

    AVLTreeNode* _left;
    AVLTreeNode* _right;
    AVLTreeNode* _parent;

    int _bf;

    AVLTreeNode(const pair<K, V>& kv) 
        :_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
    {}
}

为什么要有 parent 指针?

插入后需要从当前节点开始逐层向上更新平衡因子,以避免实现过于复杂

二. 平衡因子的更新规则

在 AVL 树中,平衡因子定义为右子树高度与左子树高度的差值(bf = rightHeight - leftHeight)。当我们插入一个新节点时,逻辑上会先将其作为叶子节点挂载,随后由下而上依次调整祖先节点的平衡因子

更新的基本逻辑如下:

  • 若新增节点在 parent 的右子树,则 parent->bf++

  • 若新增节点在 parent 的左子树,则 parent->bf--

更新后,我们需要根据 parent->bf 的值分为以下三种情况来决定是否继续向上追溯:


情况1:BF 变为 0(局部填平)

平衡因子由 1 -> 0 或 -1 -> 0,这说明在插入前,parent 子树原本是一边高一边低的,而新增节点恰好插入到了较矮的那一边

插入后 parent 两边达到等高,该子树的整体高度并没有发生变化。既然高度没变,就不会影响更高层祖先的平衡因子。停止更新,插入结束

更新到 3 为根的节点,由于高度不变不会影响上一层,更新结束

情况 2:BF 变为 ±1(局部增高)

平衡因子由 0 -> 1 或 0 -> -1,这说明在插入前,parent 子树的两边是完全等高的。新增节点的插入打破了这种平衡,导致子树变得一边高一边低

虽然此时 parent 子树依然符合 AVL 树的平衡要求,但其整体高度增加了 1 层 。这一高度变化会影响到 parent 的父节点。所以继续向上更新。重复上述逻辑检查 parent -> parent 的平衡因子

一直向上更新最坏到根节点停止

情况 3:BF 变为 ±2(局部断裂)

平衡因子由 1 -> 2 或 -1 -> -2,这说明在插入前,parent 子树已经处于失衡的边缘(BF = ±1),而新节点偏偏又插入到了原本就较高的那一边

此时必须通过旋转操作进行修复。旋转的目的是将 parent 子树重新调整为平衡状态,并使子树高度恢复到插入前的水平。完成旋转后即可停止更新,插入过程结束

更新到 10 节点,平衡因子为 2,子树已经不平衡需要旋转处理

如果更新路径一直追溯到了根节点(root),且根节点的平衡因子变为 ±1。此时由于根节点已无父节点,高度的变化无法再向上递推,更新过程也会自然停止


插入结点及更新平衡因子的代码实现

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
    if(_root == nullptr)
    {
        _root = new Node(kv);
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;

    while(cur)
    {
        if(cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else(cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }
    cur = new Node(kv);
    if(parent->_kv.first < kv.first) parent->_right = cur;  
    else parent->left = cur;
    
    cur->_parent = parent;

    // 更新平衡因子
    while(parent)
    {
        if(cur == parent->_left) parent->_bf--;
        else parent->_bf++;

        if(parent->_bf == 0) break; // 更新结束
        else if(parent->_bf == 1 || parent->_bf == -1) 
        {
            // 继续往上走
            cur = parent;
            parent = parent->_parent;
        }
        else if(parent->_bf == 2 || parent->_bf == -2)
        {
            // 此时需要旋转
            break;
        }
        else
        {
            assert(false); // 程序出现漏洞
        }
    }
    return true;
}

三. 旋转

当平衡因子的绝对值达到 2 时,就必须通过旋转来重新调整树的结构。旋转总共分为四种,左单旋 / 右单旋 / 左右双旋 / 右左双旋

旋转的基本原则

无论执行哪种旋转,都必须严格遵守以下两个底线:

  1. 保持性质:旋转后,整棵树依然必须满足左小右大的排序规则

  2. 降低高度:旋转不仅要让子树恢复平衡,更重要的是要将其高度恢复到插入节点之前的水平,从而切断对祖先节点的影响


3.1 右单旋(Right Rotation)

通常用于处理**左左(LL)**场景,即新节点插入在较高左子树的左侧,导致祖先节点左边过重

1. 触发场景分析

我们以节点 10 为根节点为例。假设其左孩子为 5,左孩子 5 的左右子树分别为 a 和 b,而 10 的右子树为 c,假设子树 a, b, c 的高度均为 h。此时 10 的平衡因子为 -1(左边比右边高 1 层)

当我们在子树 a 中插入一个新节点,导致 a 的高度从 h 增加到 h+1,从而使节点 5 的平衡因子变为 -1,而根节点 10 的平衡因子从 -1 变为 -2。此时,以 10 为根的子树违反了平衡规则,且重心明显偏向左侧

2. 旋转步骤

为了重新平衡,我们需要将 10 所在的轴线向右压下去。基于 5 < b < 10 的大小关系,旋转步骤如下:

  1. 将节点 5 的右子树 b 挂载到节点 10 的左边(因为 b 中的值均大于 5 且小于 10)

  2. 将节点 10 降级,作为节点 5 的右孩子,将节点 5 提升为这棵局部子树的新根节点。

结果验证

调整后,搜索树仍符合 a < 5 < b < 10 < c 的规则,结构保持完整。旋转操作使得节点 5 和 10 的平衡因子恢复为 0,局部子树的高度恢复至插入前的 h+2 层。这意味着该子树对外部父节点的高度影响与插入前完全一致

右单旋不仅精准地解决了左重问题,还通过降低高度避免了平衡因子向更高层祖先继续追溯

右单旋代码实现

cpp 复制代码
void RotateR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    
    // 先记录下父节点
    Node* parentParent = parent->_parent;

    parent->_left = subLR;
    if(subLR) // 防止 subLR 不存在的情况
        subLR->_parent = parent;

    subL->_right = parent;
    parent->_parent = subL;

    // parent有可能是整棵树的根,也可能是局部的子树
    // 如果是整棵树的根,要修改 _root
    // 因为这时候 _root 还在指向 parent
    if(parentParent == nullptr)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else // 如果是局部则要跟上一层链接
    {
        if(parent == parentParent->_left)
            subL = parentParent->_left;
        else
            subL = parentParent->_right;

        subL->_parent = parentParent;
    }
    parent->_bf = subL->_bf = 0;
}

3.2 左单旋**(Left Rotation)**

左单旋是右单旋的镜像操作,专门用于处理**右右(RR)**场景,即新节点插入在较高右子树的右侧,导致祖先节点重心向右严重偏移

1. 触发场景分析

我们以节点 10 为局部根节点进行说明。假设其右孩子为 15,10 的左子树为 a,15 的左右子树分别为 b 和 c。子树 a, b, c 的高度均为 h。此时节点 10 的平衡因子为 1(右边比左边高 1 层)。

当我们在子树 a 中插入一个新节点,导致 a 的高度从 h 增加到 h+1。节点 15 的平衡因子变为 1,而根节点 10 的平衡因子从 1 递增为 2。此时,以 10 为根的子树右侧过重,违反了 AVL 树的平衡准则

2. 旋转步骤

为了恢复平衡,我们需要将 10 所在的轴线向左拉下来。根据搜索树性质 10 < b < 15,旋转逻辑如下:

  1. 将节点 15 的左子树 b 挂载到节点 10 的右边(因为 b 中的所有值都大于 10 且小于 15)

  2. 将节点 10 降级,作为节点 15 的左孩子。将节点 15 提升为该局部子树的新根节点

结果验证

经过调整后,整体结构仍严格保持 a < 10 < b < 15 < c 的有序关系。旋转操作完成后,节点10和节点15的平衡因子均归零。并且子树高度已从插入后的 h+3 恢复到插入前的 h+2 水平

将右子树左侧的负载转移给原根节点,同时让原右子节点成为新的根节点。由于旋转后树的高度与插入前保持不变,这种调整不会影响更高层级的祖先节点

左单旋代码实现

cpp 复制代码
void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    parent->_right = subRL;
    if(subRL)
        subRL->_parent = parent;

    Node* parentParent = parent->_parent;

    subR->_left = parent;
    parent->_parent = subR;

    // 链接上层节点
    if (parentParent == nullptr)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (parent == parentParent->_left)
        {
            parentParent->_left = subR;
        }
        else
        {
            parentParent->_right = subR;
        }
        subR->_parent = parentParent;
    }
    parent->_bf = subR->_bf = 0;
}

3.3 左右双旋 (Left-Right Rotation)

为什么单旋在此时失效?

当节点 10 的左子树 5 过高,且高度增加是由于 5 的右子树(即孙子节点 8 所在的路径)增高引起的,这被称为 LR 型失衡

如果此时强行执行右单旋,节点 5 的右子树(较重的部分)会变成节点 10 的左子树,重心只是从一侧移动到了另一侧,整棵树依然不平衡。因此,我们需要通过两次旋转:先对子树执行左单旋将其拉直,再对整体执行右单旋将其调平


核心逻辑

我们以以下 AVL 树为例。双旋最精妙的地方在于平衡因子的修复。旋转本身是固定的(先 RotateL(5),再 RotateR(10)),但旋转后各个节点的 BF 取值取决于新节点究竟插在了孙子节点 8 的哪一侧

我们设定节点 8 为旋转前 5 的右孩子。根据插入位置的不同,分为以下三个场景:

场景 1:插在孙子左侧 (h >= 1)

新节点插入在 8 的左子树 e,导致 8 的平衡因子变为 -1。旋转后 8 成为新的根节点,两边等高,其 BF = 0。5 的右边被 8 的左子树抵消,其 BF = 0。10 的左边由于只接纳了 8 的右子树(相对较短),其 BF = 1

场景 2:插在孙子右侧 (h >= 1)

新节点插入在 8 的右子树 f,导致 8 的平衡因子变为 1。旋转后 8 成为新的根节点,其 BF = 0。10 的左边被 8 的右子树抵消,其 BF = 0。而5 的左边由于只保留了原来的高度,其 BF = -1

场景 3:孙子本身就是新增节点 (h = 0)

节点 8 本身就是新插入的节点。此时 8 的平衡因子为 0

这是一种最简单的重排。旋转后,8 作为根,5 和 10 分居左右。三个节点的平衡因子均修复为 0

左右双旋的本质是提拔孙子做爷爷。 通过两次旋转,原本位于深层的节点 8 被提升到了局部根节点的位置,而原本的 5 和 10 顺理成章地成为了它的左右孩子


代码实现

cpp 复制代码
void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    int bf = subLR->_bf;

    // 复用单旋的代码
    RotateL(subL);
    RotateR(parent);

    // 更新平衡因子
    if(bf == -1)
    {
        subLR->_bf = 0;
        subL->_bf = 0;
        parent->_bf = 1;
    }
    else if(bf == 1)
    {
        subLR->_bf = 0;
        subL->_bf = -1;
        parent->_bf = 0;
    }
    else if(bf == 0)
    {
        subLR->_bf = 0;
        subL->_bf = 0;
        parent->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

在实现代码时,一定要在旋转前记录下孙子节点 8 的平衡因子。因为单旋函数内部会修改节点的 BF,如果不提前记录,旋转结束后将无法判断该如何正确修复 5 和 10 的平衡因子


3.4 右左双旋 (Right-Left Rotation)

右左双旋是左右双旋的镜像操作,专门用于处理 **右左(RL)**型失衡。这种失衡发生在根节点的右子树较高,且高度的增加是由右孩子的左子树引起的

与单旋不同,RL 失衡呈现折线状,需要先通过一次右单旋将路径拉直,再通过一次左单旋完成最终的调平


核心逻辑

以以下这棵 AVL 树为例,假设局部根节点为 10,其右孩子为 15。失衡是由于 15 的左孩子 12 所在的子树高度增加导致的

  • 第一步:右单旋。以 15 为旋转点执行右单旋,将 12 提升,使 10、12、15 三者在逻辑上连成一条向右倾斜的直线

  • 第二步:左单旋。以 10 为旋转点执行左单旋,将 12 提升为新的局部根节点,10 和 15 分列其左右

平衡因子的修复规则

双旋的核心难点在于旋转完成后平衡因子的修正。我们根据新节点插入的具体位置,分为以下三个场景进行讨论:

场景 1:新增节点插入在 12 的左子树 e (h >= 1)

插入后,12 的平衡因子更新为 -1。经过两次旋转,12 成为根节点

节点 10 的右边接纳了 e 的增高部分,趋于平衡,其 BF = 0。节点 15 的左边失去了原本的支撑,相对较矮,其 BF = 1。节点 12 左右均衡,其 BF = 0

场景 2:新增节点插入在 12 的右子树 f (h >= 1)

插入后,12 的平衡因子更新为 1

节点 15 的左边接纳了平衡部分,其 BF = 0。节点 10 的右边相对较低,其 BF = -1。节点 12 成为新根,其 BF = 0

场景 3:节点 12 本身即为新增节点 (h = 0)

12 插入后其平衡因子为 0

这是一次标准的重新排布。旋转完成后,12 作为根,10 和 15 完美对称。三个节点 10、12、15 的平衡因子全部修复为 0

代码实现

cpp 复制代码
void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;

    RotateR(parent->_right);
    RotateL(parent);

    if (bf == 0)
    {
        subR->_bf = 0;
        subRL->_bf = 0;
        parent->_bf = 0;
    }
    else if (bf == 1)
    {
        subR->_bf = 0;
        subRL->_bf = 0;
        parent->_bf = -1;
    }
    else if (bf == -1)
    {
        subR->_bf = 1;
        subRL->_bf = 0;
        parent->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

在实现代码时,双旋函数其实是简单的组合调用,例如 RL 双旋只需 RotateR(parent->right) 紧接着 RotateL(parent)。真正的难点在于 if-else 判断逻辑:必须在旋转之前通过孙子节点的平衡因子确定属于哪种场景,从而在旋转后正确地手动重置各个节点的平衡因子


小结

通过分析这四种旋转操作,我们可以提炼出 AVL 树维持平衡的核心机制:

  • 单旋(LL/RR):处理直线型结构,通过一次支点摆动恢复平衡

  • 双旋(LR/RL) :处理折线型结构,本质上是先拉直、后调平

四. AVL 树的插入

AVL 插入可以理解为 BST 插入 + 平衡维护

在新增节点挂载到树上后,该节点会导致其父节点乃至更高层祖先节点的高度发生变化。因此,我们需要沿着新增节点 -> 根节点的路径,依次更新祖先节点的平衡因子。在更新过程中,通常会出现以下几种情况:

1. 局部高度未变,提前结束更新

并非所有的更新都会追溯到根节点。如果在更新过程中,某个祖先节点的平衡因子变为 0,这意味着该子树的高度在插入前后保持不变(即新节点填补了原有的低洼处),此时高度变化不再向上传递,更新流程可以提前结束

2. 局部高度增加,继续向上追溯

如果祖先节点的平衡因子更新后变为 ±1,说明该子树的高度增加了一层,但尚在平衡允许范围内。这种高度的变化可能会影响更上层的祖先,因此更新过程必须继续沿着路径向根节点推进,最极端的情况会一直更新至根节点

3. 触发失衡

如果在更新过程中,某个节点的平衡因子达到了 ±2,则说明该子树已经失去平衡。此时,我们需要根据具体类型(LL、RR、LR 或 RL)进行对应的旋转操作


代码实现

补充平衡因子更新机制的实现

cpp 复制代码
// 迭代更新平衡因子与旋转维护
while (parent) {
    // 根据插入位置更新父节点平衡因子(BF)
    if (cur == parent->_left) parent->_bf--;
    else parent->_bf++;

    // 检查平衡因子状态,处理不同情况
    if (parent->_bf == 0) {
        // 情况 1:填平低洼,子树高度未发生变化,无需继续向上更新
        break;
    } 
    else if (parent->_bf == 1 || parent->_bf == -1) {
        // 情况 2:子树高度增加,需要继续向上追溯更新祖先节点
        cur = parent;
        parent = parent->_parent;
    } 
    else if (parent->_bf == 2 || parent->_bf == -2) {
        // 情况 3:当前节点触发失衡,根据失衡类型进行旋转处理
        if (parent->_bf == 2 && cur->_bf == 1) {
            RotateL(parent); // RR型失衡 -> 左单旋
        } 
        else if (parent->_bf == -2 && cur->_bf == -1) {
            RotateR(parent); // LL型失衡 -> 右单旋
        } 
        else if (parent->_bf == 2 && cur->_bf == -1) {
            RotateRL(parent); // RL型失衡 -> 右左双旋
        }     
        else if (parent->_bf == -2 && cur->_bf == 1) {
            RotateLR(parent); // LR型失衡 -> 左右双旋
        }

        // 旋转后子树高度恢复平衡,平衡因子修复完成,退出循环
        break;
    } 
    else {
        // 理论上不会执行到此处,若触发说明插入前AVL树已处于失衡状态
        assert(false);
    }
}

五. AVL 树的查找

查找操作是 AVL 树中最省心的部分。由于 AVL 树在插入阶段已经通过复杂的旋转将平衡性维护到了极致,在查找时,我们完全不需要担心树结构会退化。其查找逻辑与标准的二叉搜索树(BST)完全一致,本质上是一种基于路径过滤的分治思想

从根节点开始,将目标值 x 与当前节点进行比对:若 x 较小则转向左子树,较大则转向右子树,相等则直接返回命中节点。得益于 AVL 树高度 H 始终严格维持在 log N 附近,其查找的最坏时间复杂度被稳定控制在 O(log N)。即便面对海量数据,查找路径也极其短促且性能波动极小

代码实现

cpp 复制代码
Node* Find(const K& key) 
{
    Node* cur = _root;
    while (cur) 
    {
        if (cur->_kv.first < key) {
            cur = cur->_right;
        } else if (cur->_kv.first > key) {
            cur = cur->_left;
        } else {
            return cur; // 命中目标
        }
    }
    return nullptr; // 查找失败
}

普通 BST 的查找效率极易受到插入顺序的影响,最坏情况下会退化为 O(N)。而 AVL 树通过在插入时付出微小的旋转成本,换取了极其稳定的查询性能。对于读多写少的业务场景,AVL 树带来的检索稳定性具有极高的工程价值

六. AVL 树的平衡检测 (Verification)

在处理海量随机数据时,仅凭肉眼观察几个简单的用例是远远不够的。我们需要编写一个自动化的平衡检测函数,通过递归遍历整棵树,验证其是否严格遵守 AVL 树的定义

要判定一棵二叉树是否为合法的 AVL 树,必须同时满足以下两个硬性条件:

  1. 每一个节点的左右子树高度差(平衡因子)的绝对值不超过 1,即 h_right - h_left <= 1

  2. 节点中存储的平衡因子 _bf 必须与该节点实际计算出的高度差完全吻合。

1. 求子树高度的辅助函数

由于平衡检测依赖于高度值,我们首先需要实现一个递归求高度的函数

cpp 复制代码
int _Height(Node* root)
{
    if(root == nullptr)
        return 0;

    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);

    // 当前节点高度 = 左右子树中较高者 + 1
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

2. 平衡检测核心逻辑

检测过程采用前序遍历的思路:先检查当前节点是否平衡,再递归检查左子树和右子树

cpp 复制代码
bool _IsBalance(Node* root)
{
    if(root == nullptr)
        return true;

    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);
    int diff = rightHeight - rightHeight;

    // 检查高度差的绝对值是否超过1
    if(abs(diff) >= 2)
    {
        std::cout << root->_kv.first << " 失衡,平衡因子为:" << diff << std::endl;
        return false;
    }
    // 检查存储的平衡因子与实际高度差是否一致
    if(diff != root->_bf)
    {
        std::cout << root->_kv.first << " 平衡因子异常,实际为:" << diff << std::endl;
        return false;
    }

    // 递归检查左右子树是否平衡
    return _IsBalance(root->_left) && _IsBalance(root->_right);
}

时间复杂度为 O(N log N)(由于 _Height 函数会被频繁调用)。虽然在生产环境中性能开销较大,但在开发测试阶段,它作为验证逻辑正确性的黄金标准仍然非常可靠


测试用例

统计节点数量的辅助函数 Size

cpp 复制代码
size_t _Size(Node* root)
{
    if(root == nullptr)
        return 0;

    // 递归逻辑:左子树节点数 + 右子树节点数 + 根节点(1)
    return _Size(root->_left) + _Size(root->_right) + 1;
}

size_t Size()
{
    return _Size(_root);
}

具体测试用例

cpp 复制代码
void TestAVLTree()
{
    const int N = 100000;
    vector<int> v;
    v.reserve(N);
    srand((unsigned int)time(0));

    // 1. 构造随机数据集
    for (size_t i = 0; i < N; i++)
    {
        // rand() + i 增加了数据的随机区间,减少了 Key 冲突
        v.push_back(rand() + i);
    }

    // 2. 性能测试:插入耗时统计
    size_t begin = clock();
    AVLTree<int, int> t;
    for (auto e : v)
    {
        t.Insert(make_pair(e, e));
    }
    size_t end = clock();

    // 3. 结果验证
    cout << "数据总量 (N): " << N << endl;
    cout << "插入耗时 (ms): " << end - begin << endl;
    
    // 验证平衡性:调用我们之前写的 IsBalanceTree
    cout << "平衡检测结果 (1-正常/0-异常): " << t.IsBalanceTree() << endl;
    
    // 理论高度约为 1.44 * log2(N)
    cout << "树的实际高度: " << t.Height() << endl;

    // 验证实际插入的节点数(排除重复项)
    cout << "树中节点总数: " << t.Size() << endl;

    // 4. 数据一致性校验(可选)
    for (auto e : v)
    {
        if (t.Find(e) == nullptr)
        {
            assert(false); // 如果插入的值找不到,说明逻辑有误
        }
    }
    cout << "数据查找一致性校验通过!" << endl;
}

通过 10^5 级别的随机压力测试,我们可以看到 AVL 树将高度严格控制在 20 层以内。这意味着即便在十万级的数据量下,每一次查找也仅仅需要不到 20 次的比较。这种极致的平衡,正是 AVL 树在读多写少场景下立足的基石

七. 总结

AVL 树作为计算机科学中最早被发明的自平衡二叉搜索树,其核心特征在于其近乎苛刻的逻辑约束。通过对平衡因子的严格监控以及四种旋转策略的灵活运用,AVL 树确保了任何规模的数据下,查找、插入和删除的时间复杂度都能稳稳锁定在 O(log N)

理解 AVL 树不仅是掌握一种高效搜索结构的过程,更是对递归思维、逻辑严谨性和空间想象力的全面锻炼。在进阶学习中,我们会接触到更通用的平衡方案------红黑树。红黑树通过适当放宽平衡条件实现了更好的综合性能,而要真正理解红黑树,首先需要深刻领会 AVL 树所体现的绝对平衡逻辑

相关推荐
小龙报1 小时前
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现
c语言·数据结构·c++·物联网·算法·链表·visualstudio
噜啦噜啦嘞好2 小时前
算法:双指针
数据结构
仟濹2 小时前
【算法打卡day20(2026-03-12 周四)算法:前缀和,二维前缀和,快慢指针,哈希表set使用技巧,哈希表map使用技巧】7个题
数据结构·算法
一叶落4382 小时前
LeetCode 67. 二进制求和(C语言详解 | 双指针模拟加法)
c语言·数据结构·算法·leetcode
mmz12072 小时前
贪心算法(c++)
c++·贪心算法
xh didida2 小时前
数据结构--队列
数据结构·算法
vx-程序开发2 小时前
springboot具备推荐和预警机制的大学生兼职平台的设计与实现-计算机毕业设计源码17157
java·c++·spring boot·python·spring·django·php
美式请加冰2 小时前
位运算符的介绍和使用
数据结构·算法
tankeven2 小时前
HJ127 小红的双生串
c++·算法