【高阶数据结构】二叉树进阶探秘: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

相关文章
|
24天前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
57 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
24天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
41 12
|
24天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
40 10
|
24天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
41 2
|
2月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
92 5
|
3月前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
119 4
|
3月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
308 9
|
3月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
48 1
|
24天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
134 77

热门文章

最新文章