【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(二)

简介: 【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解

【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(一)https://developer.aliyun.com/article/1617411


3.3 双旋解决问题

如果出现了这种if (parent->_bf == 2 || cur->_bf == -1), if (parent->_bf == -2 || cur->_bf == 1)普通单旋是不能解决问题的,需要使用到双旋。

如果使用单旋的话是没有多大作用的,做无用功。我们使用单旋处理的情况是一边独高,而不是那边高,这边高。对此可以先对内部旋转变成单独一边高,再使用单旋进行调整AVL树

3.3.1 新节点插入较高左子树的右侧–左右:先左单旋再右单旋

场景:parent->_bf == -2 && cur->_bf == 1

这里可以直接复用实现好单旋的接口,但是需要对平衡因子进行调整,这里平衡因子可以通过结论直接修改。

3.3.2 根据结论修改AVL树节点平衡因子

通过图中对于不同情况的分析,对于修改平衡因子的结果是根据SubLR平衡因子特殊值决定的。b、c分别给谁,会影响平衡因子的分配。

3.3.3 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

场景:parent->_bf == 2 && cur->_bf == -1

这里还是需要根据60这块的平衡因子去判断的,b c分别给谁,会影响平衡因子的分配。左边新增两次旋转给到右边,右边新增两次旋转给到左边,通过结论最后修改平衡因子。

void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;
    RotateR(subR);
    RotateL(parent);
    subRL->_bf = 0;
    if (bf == 1)
    {
        subR->_bf = 0;
        parent->_bf = -1;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        subR->_bf = 1;
    }
    else
    {
        parent->_bf = 0;
        subR->_bf = 0;
    }
}
void RotateLR(Node* parent)
{
    Node* SubL = parent->_left;
    Node* SubLR = SubL->_right;
    int _bf = SubLR->_bf;
    RotateL(parent->_left);
    RotateR(parent);
    if (_bf == 0)
    {
        parent->_bf = 0;
        SubL->_bf = 0;
        SubLR->_bf = 0;
    }
    else if (_bf == 1)
    {
        parent->_bf = 0;
        SubL->_bf = -1;
        SubLR->_bf = 0;
    }
    else if (_bf == -1)
    {
        parent->_bf = 1;
        SubL->_bf = 0;
        SubLR->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

3.3.4 小总结

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑 :

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
  • 当pSubR的平衡因子为1时,执行左单旋
  • 当pSubR的平衡因子为-1时,执行右左双旋
  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
  • 当pSubL的平衡因子为-1是,执行右单旋
  • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新

四、AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分为两步:验证是否为二叉搜索树,验证是否为平衡树

4.1 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

void InOder()
{
    _InOder(_root);
    cout << endl;
}
private:
void _InOder(Node* _root)
{
    if (_root == nullptr)
    {
        return;
    }
    
    InOder(_root->_left);
    cout << _root->_kv.first << " " << _root->_kv.second << endl;
    InOder(_root->_right);
}

这里将中序遍历实现逻辑封装到私有成员函数中隐藏它的具体实现细节,使得外部用户无法直接访问该函数,提高了代码的安全性和可维护性,符合面向对象编程的封装性原则。这里外部访问中序遍历接口,只是传递了节点指针的副本,而不修改任何节指针的实际值。

4.2 验证其为平衡树

规则:

  • 每个节点子树高度差的绝对值不超过1(注意节点种种那个如果没有平衡因子)
  • 节点的平衡因子是否计算正确
bool IsBalance()
{
    return _IsBalance(_root);
}
int Height()
{
    return _Height(_root);
}
int Size()
{
    return _Size(_root);
}
private:
int _Size(Node* root)
{
    return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
    if (root == nullptr)
        return 0;
    return max(_Height(root->_left), _Height(root->_right)) + 1;
}
bool _IsBalance(Node* root)
{
    if (root == nullptr)
        return true;
    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);
    // 不平衡
    if (abs(leftHeight - rightHeight) >= 2)
    {
        cout << root->_kv.first << endl;
        return false;
    }
    // 顺便检查一下平衡因子是否正确
    if (rightHeight - leftHeight != root->_bf)
    {
        cout << root->_kv.first << endl;
        return false;
    }
    return _IsBalance(root->_left)
        && _IsBalance(root->_right);
}
void _InOder(Node* _root)
{
    if (_root == nullptr)
    {
        return;
    }
    InOder(_root->_left);
    cout << _root->_kv.first << " " << _root->_kv.second << endl;
    InOder(_root->_right);
}

五、AVL树的删除(了解 )

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置 ,这里调正平衡因子的逻辑就需要反过来,同时参考下二叉搜索树的删除操作。

六、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log^n^)`。但是如果要对AVL树做一些结构修改的操作,性能非常低下

插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

补充调式小技巧:

void TestAVLTree1()
{
    //int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    AVLTree<int, int> t1;
    for (auto e : a)
    {
        /*if (e == 4)
    {
      int i = 0;
    }*/
        // 1、先看是插入谁导致出现的问题
        // 2、打条件断点,画出插入前的树
        // 3、单步跟踪,对比图一一分析细节原因
        t1.Insert({e,e});
        cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;
    }
    t1.InOrder();
    cout << t1.IsBalance() << endl;
}


【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(三)https://developer.aliyun.com/article/1617413

相关文章
|
6月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
188 10
 算法系列之数据结构-二叉树
|
5月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
468 1
|
6月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
157 3
 算法系列之数据结构-Huffman树
|
6月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
506 19
|
8月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
193 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
8月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
184 12
|
8月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
164 10
|
8月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
261 3
|
10月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
223 59
|
3月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
52 0
栈区的非法访问导致的死循环(x64)