【C++进阶】AVL树(来自二叉搜索树的复仇)

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

 主厨的主页:Chef‘s blog  

 所属专栏:c++大冒险
 

 总有光环在陨落,总有新星在闪烁


引言:

    之前我们学习了二叉搜索树,有了它我们查找数据效率会很高,但是,有时候查找效率却很低

比如下面的情况:

 

      我们称之为歪脖子树,可以看到他的搜索效率又退化到了O(N),为了解决这个问题,我们今天就来学习二叉搜索树plus——AVL树

注:没有学习二叉搜索树的朋友建议先来看看这篇博客哦:

大战二叉搜索树

一.AVL树的概念

      两位俄罗斯的数学家G.M.Adelson-Velski和E.M.Landis在1962年发明了AVL树,解决了上述问题,
AVL树或者是空树,或者是具有以下性质的二叉搜索树:
  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1

     通过控制子树高度差,让AVL树几乎完美接近于平衡,便不会出现单支树的情况,保证了优良的搜索性能,因此AVL树又称为高度平衡二叉搜索树。 

二. AVL树节点的模拟

template<class K,class V>
struct AVLNode
{AVLNode<K, V>*_left;// 该节点的左孩子AVLNode<K, V>*_right;// 该节点的右孩子AVLNode<K, V>* _parent;// 该节点的双亲pair<K, V> _val;  // 该节点存储的数值int _bf;// 该节点的平衡因子(balance factor)AVLNode(pair<K,V> val=pair<K,V>()):_left(nullptr), _right(nullptr), (nullptr),_val(val)_bf(0);{}
};

细节:

  1. 使用三叉链,分别是指向左节点,右节点和双亲节点
  2. 使用KV模型,数据存在于pair对象,而不是直接存在于节点
  3. 结点存储平衡因子,用来记录左右子树高度差(右树高度-左树高度)

三.AVL树模拟

3.1成员变量

template<class K,class V>
class AVLTree
{typedef AVLNode<K, V> Node;
public://函数
protected:AVLNode* _root;
};

3.2 插入

     因为AVL树也是二叉搜索树,所以默认成员函数和遍历与之前写的没什么不同,只是插入方式改变了(使得他能成为平衡树),所以这里重点讲解AVL树的插入。

3.2.1AVL树的插入过程可以分为两步:

  • 1. 按照二叉搜索树的方式插入新节点
  • 2. 调整节点的平衡因子
bool Insert(const pair<K, V>& val)
{if (_root == nullptr){_root = new Node(val);return true;}else{Node*cur=_root;Node*parent=nullptrwhile (cur){parent = cur;if (cur->_val > val)cur = cur->left;else if (cur->_val < val)cur = cur->_right;elsereturn false;}cur = new Node(val);if (parent->_val.first>cur->_val.first){parent->_left = cur;}else{parent->_parent = cur;}cur->_parent = parent;
//cur插入后,parent的平衡因子一定需要调整,在插入之前,parent
//的平衡因子分为三种情况:-1,0, 1while (parent)//向上回溯检测平衡因子{
//, 插入则分以下两种情况://1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可//2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可if (parent->_left == cur)parent->_bf--;elseparent->_bf++;
//此时:parent的平衡因子可能有三种情况:0,正负1, 正负2//1. 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整
//成0,此时满足AVL树的性质,插入成功,停止循环if (parent->_bf == 0)break;//平衡了,不用检测了
//2. 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更
//新成正负1,此时以parent为根的树的高度增加,需要继续向上更新else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}
// 3. 如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进
//行旋转处理else if (parent->_bf == 2 || parent->_bf == -2)//进行旋转{if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}}
//现在bf绝对值大于2,说明插入之前就已经不是AVL树结构,则直接断言报错elseassert(0);}}
}

  3.2.2 注意事项:


    可能有老铁觉得bf绝对值为1时也符合AVL树结构,应该直接跳出循环,然而事实是:

  • 1.这棵树现在bf绝对值是1说明之前是0,
  • 2.他的父亲节点的bf可能因为他的bf改变而改变
  • 3.或许他父亲原来bf就是1,在它的影响下就会变成2因此要一直回溯检验父亲,祖父........

     3.2.3关于平衡因子的变动:

1.插入后bf为0

分析:

         插入的节点插在了短的一边正好,消除了左右子树高度差

2.插入后bf为1或-1

 分析

       此时增加了局部子树的高度,不确定有没有影响父亲的高度差,所以要向上回溯调查

四:旋转

      在一棵原本是平衡的 AVL 树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL 树的旋转分为两种:  单旋和双旋,其中单旋又分为右旋和左旋,双旋分为右左旋和左右旋

4.1. 新节点插入较高左子树的左侧---左左:右单旋

       上图在插入前, AVL 树是平衡的,新节点插入到 30 的左子树 ( 注意:此处不是左孩子 ) 中, 30 左子树增加 了一层,导致以 60 为根的二叉树不平衡,要让 60 平衡,只能将 60 左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60 转下来,因为 60 30 大,只能将其放在 30 的右子树,而如果 30 有右子树,右子树根的值一定大于30 ,小于 60 ,只能将其放在 60 的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下情况需要考虑:
  •  1. 30节点的右孩子可能存在,也可能不存在
  •  2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树
RotateR(AVLNode*parent)//右旋
{Node* grandparent = parent->_parent;Node* ChildL = parent->_left;if (grandparent){if (grandparent->_left == parent)grandparent->_left = ChildL;elsegrandparent->_right = ChildL;}else_root = ChildL;ChildL->_parent = grandparent;//两两一组进行改变parent->_left = ChildL->_right;ChildL->_right->_parent = parent;ChildL->_right = parent;parent->_parent = ChildL;//ChildL->_bf = parent->_bf = 0;
}

4.2. 新节点插入较高右子树的右侧---右右:左单旋

情况与右旋类似,只要把修改对象ChildL和ChildL的右子树转化为ChildR和他的ChildR左子树即可

RotateL(AVLNode*parent)//左旋
{Node* grandparent = parent->_parent;Node* ChildR = parent->_right;if (grandparent){if (grandparent->_left == parent)grandparent->_left = ChildR;elsegrandparent->_right = ChildR;}else_root = ChildR;ChildR->_parent = grandparent;parent->_right = ChildR->_left;ChildR->_left->_parent = parent;ChildR->_left = parent;parent->_parent = ChildR;ChildR->_bf = parent->_bf = 0;
}

4.3. 新节点插入较高右子树的左侧---右左:右左旋

      将双旋变成单旋后再旋转,即:先对90进行右单旋,然后再对30进行左单旋,旋转完成后再考虑平衡因子的更新。

RotateRL(AVLNode*parent)//双旋,先右旋在左旋
{Node* ChildR = parent->_right;int bf = ChildR->_left->_bf;RotateR(ChildR);RotateL(parent);if (bf == 0){parent->_bf = 0;ChildR->_bf = 0;ChildR->_left->_bf = 0;}else if (bf == 1){parent->_bf = -1;ChildR->_bf = 0;ChildR->_left->_bf = 0;}else if (bf == -1){parent->_bf = 0;ChildR->_left->_bf = 0;ChildR->_bf = 1;}else{assert(false);}
}

4.4. 新节点插入较高左子树的右侧---左右:左右旋

RotateLR(AVLNode*parent)//双旋,先左旋,再右旋
{Node* ChildL = parent->_left;int bf = ChildL->_right->_bf;RotateR(ChildL);RotateL(parent);if (bf == 0){parent->_bf = 0;ChildL->_bf = 0;ChildL->_right->_bf = 0;}else if (bf == 1){parent->_bf = 0;ChildL->_bf = -1;ChildL->_right->_bf = 0;}else if (bf == -1){parent->_bf = 1;ChildL->_right->_bf = 0;ChildL->_bf = 0;}else{assert(false);}
}

 旋转总结:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
  • 1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR 当pSubR的平衡因子为1时,执行左单旋当pSubR的平衡因子为-1时,执行右左双旋
  • 2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL 当pSubL的平衡因子为-1是,执行右单旋 当pSubL的平衡因子为1时,执行左右双旋旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

5 AVL树的验证

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

5.1. 验证其为二叉5.搜索树

如果 中序遍历可得到一个有序的序列 ,就说明为二叉搜索树
	void Inorde(Node* root,vector<pair<K,V>>&v){if (root == nullptr)return;Inorde(root->_left, v);v.push_back(root->_val);Inorde(root->_right, v);}

5.2. 验证其为平衡树

  1. 每个节点子树高度差的绝对值不超过1
  2. 节点的平衡因子是否计算正确
int high(Node* root)
{if (root == nullptr)return 0;int left = high(root->left);int right = high(root->right);int x = left > right ? left : right;return 1 + x;
}
bool _IsBalanceTree(Node* pRoot)
{// 空树也是AVL树if (nullptr == pRoot) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);int diff = rightHeight - leftHeight;
// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != pRoot->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot-
>_pRight);}

6. AVL树的性能

6.1优势:

     AVL 树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过 1 ,这 样可以保证查询时高效的时间复杂度,即log(N)

6.2劣势:

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

结语:

今天我们学习了AVL树,他是二叉搜索树的plus,我们主要是对他的元素插入、旋转进行了探讨,接着学习了如何验证是否为AVL树,最后了解了他的优势与劣势。
那么,我们红黑树再见喽,下次一起手撕红黑树!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/589599.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0配置React

在本地安装和配置React项目&#xff0c;您可以使用create-react-app这个官方推荐的脚手架工具。以下是安装React的步骤&#xff0c;包括安装Node.js、使用create-react-app创建React应用&#xff0c;以及启动开发服务器。 下载安装node.js运行以下命令&#xff0c;验证Node.js…

字符分类函数

字符分类函数 C语言中有⼀系列的函数是专门做字符分类的&#xff0c;也就是⼀个字符是属于什么类型的字符的。这些函数的使用都需要包含⼀个头文件是 ctype.h 这些函数的使用方法非常类似&#xff0c;我们就讲解⼀个函数的事情&#xff0c;其他的非常类似&#xff1a; int i…

用Vue仿了一个类似抖音的App

大家好&#xff0c;我是 Java陈序员。 今天&#xff0c;给大家介绍一个基于 Vue3 实现的高仿抖音开源项目。 关注微信公众号&#xff1a;【Java陈序员】&#xff0c;获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。 项目介绍 douyin —— 一个基于 Vue、Vite 实…

【软件工程】详细设计(二)

这里是详细设计文档的第二部分。前一部分点这里 4. 学生端模块详细设计 学生端模块主要由几个组件构成&#xff1a;学生登录界面&#xff0c;成绩查询界面等界面。因为学生端的功能相对来说比较单一&#xff0c;因此这里只给出两个最重要的功能。 图4.1 学生端模块流程图 4.…

女大三抱金砖?看完这篇起诉状就明白:猜疑乃婚姻之大敌

女大三抱金砖&#xff1f;看完这篇起诉状就明白&#xff1a;猜疑乃婚姻之大敌 阿勇与阿芳&#xff0c;一对年过四十的夫妻&#xff0c;且有一对已成年的儿女&#xff0c;如今走到了婚姻的尽头。原告阿勇指控双方感情早已破裂&#xff0c;受父母包办婚姻影响&#xff0c;两人经常…

应用方案D78040场扫描电路,偏转电流可达1.7Ap-p,可用于中小型显示器

D78040是一款场扫描电路&#xff0c;偏转电流可达1.7Ap-p&#xff0c;可用于中小型显示器。 二 特 点 1、有内置泵电源 2、垂直输出电路 3、热保护电路 4、偏转电流可达1.7Ap-p 三 基本参数 四 应用电路图 1、应用线路 2、PIN5脚输出波形如下&#xff1a;

makefile01

什么是makefile Makefile 文件描述了 Linux 系统下 C/C 工程的编译规则&#xff0c;它用来自动化编译 C/C 项目。一旦写编写好 Makefile 文件&#xff0c;只需要一个 make 命令&#xff0c;整个工程就开始自动编译&#xff0c;不再需要手动执行 GCC 命令。一个中大型 C/C 工程…

Unity类银河恶魔城学习记录12-2 p124 Character Stats UI源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI_Statslot.cs using System.Collections; using System.Collections.Gen…

云计算的安全需求

目录 一、概述 二、云安全服务基本能力要求 三、信息安全服务&#xff08;云计算安全类&#xff09;资质要求 3.1 概述 3.2 资质要求内容 3.2.1 组织与管理要求 3.2.2 技术能力要求 四、云安全主要合规要求 4.1 安全管理机构部门的建立 4.2 安全管理规范计划的编制 4…

回溯算法|78.子集

力扣题目链接 class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {result.push_back(path); // 收集子集&#xff0c;要放在终止添加的上面&#xff0c;否则会漏掉自…

linux 回收站机制(笔记)

Linux下回收站机制https://mp.weixin.qq.com/s/H5Y8VRcaOhFZFXzR8yQ7yg 功能 &#xff1a;设立回收站&#xff0c;并且可定时清空回收站。 一、建议将alias rm 改成别的。 比如alias rmm &#xff0c;同时修改rm -rf ~/.trash/* 改成 rmm -rf ~/.trash/* 不然影响rm 的正常使…

Axure案例分享—垂直手风琴(附下载地址)

今天分享的案例是Axure8(兼容9和10)制作的垂直手风琴 一、功能介绍 折叠或展开多个面板内容&#xff0c;默认为展开一项内容&#xff0c;点击任一收起的选项&#xff0c;展开面板&#xff0c;其他面板收起二、制作过程 原型是由矩形组件以及动态面板构成&#xff0c; 拖入一…