map和set(二)——AVL树的简单实现

引入

二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。简单来说就是之前二叉搜索树由于可能在某一个节点上一直深入,按照最坏情况算这的时间复杂度就高了起来,而AVL树这其中之一的平衡树

1.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度

AVL树又称高度平衡二叉搜索树

任何AVL树都满足一下条件:

  • 它的左右子树都是AVL树
  • 任何树及其子树的高度差(也就是平衡因子)的绝对值不超过1

 比如:

当然平衡因子不一定是必须的,它只是一种控制方式(让我们更便捷地控制这棵树) 

为何是不超过1,而不是0呢?0不是更加平衡吗?

由于树的节点是一个个插入的,无法保证绝对的平衡(有些情况无法满足高度差为0);因此高度差不超过1

2.AVL树节点的定义

和二叉搜索树类似,只不过多了个平衡因子

//AVL树的节点
template<class K,class V>
struct AVLTreeNode
{AVLTreeNode<K,V>* _left;AVLTreeNode<K,V>* _right;AVLTreeNode<K,V>* _parent;int _bf = 0;//平衡因子pair<K, V> _kv;//构造AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}};

3.AVL树的插入

3.1插入

AVL树就是在二叉搜索树的基础上引进了平衡因子(bf),因此AVL树也可以看做二叉搜索树

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


  1. 以二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

二叉搜索树的方式插入 

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 (kv.first > cur->_kv.first)//插入的值比遍历的值大{parent = cur;//cur往下遍历时,父节点同时要往下走cur = cur->_right;//往右走}else if (kv.first < cur->_kv.first)//插入的值比遍历的值小{parent = cur;cur = cur->_left;//往左走}else//说明插入的值已经存在,return false{return false;}}//走到这说明已经找到可以插入的地方 //创建一个新节点cur = new Node(kv);//判断插入的节点该连接到父节点的左还是右if (cur->_kv.first > parent->_kv.first)//cur 的值大于父节点的值,连右{parent->_right = cur;}else{parent->_left = cur;}//接下来就是判断平衡因子的时候了return true;}

 判断平衡因子

平衡因子 = 右子树高度-左子树高度

插入节点会影响哪些节点的平衡因子呢?新增节点的部分祖先

更新原则:

若cur是此父节点的左子树(节点)那么父节点的平衡因子-- ,是右子树(节点)那么父节点平衡因子++

是否继续更新取决于父节点的高度是否变化,是否会影响爷爷节点
一直往上走直至parent为nullptr(根节点的父节点为空)或者平衡因子为0或者平衡因子为2(需要旋转)

当cur(新增节点插入后),有三种情况

情况1:

更新后 父节点(parent)的平衡因子(bf)为 0 ,parent所在子树高度不变,不会影响爷爷;说明更新前parent的bf为1或-1,往父节点矮的那边插入节点,左右均衡,parent所在子树的高度不变


情况2:

更新后 父节点的平衡因子为1或-1,parent所在子树高度改变了,会影响爷爷,继续往上更新;说明更新前parent的bf为0(本身平衡了),往p的任意一边插入,使父节点变得不均衡,但不违反规则


情况3:

更新后父节点的平衡因子为2或-2,说明父节点所在的子树违反了平衡规则,需要旋转处理

		//接下来就是判断平衡因子的时候了cur->_parent = parent;while (parent){if (cur == parent->_left)//cur在父左 父bf--{parent->_bf--;}else//cur在父右 父bf++{parent->_bf++;}if (parent->_bf == 0)//父bf==0 break{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父bf为1 or -1继续往上{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//平衡因子为2 只能旋转了//右高左低 左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1)//左高右低 右单旋{RotateR(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//两边都高左右双旋{RotateLR(parent);}else//右左双旋{RotateRL(parent);}}else{assert(false);}}

合起来

	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 (kv.first > cur->_kv.first)//插入的值比遍历的值大{parent = cur;//cur往下遍历时,父节点同时要往下走cur = cur->_right;//往右走}else if (kv.first < cur->_kv.first)//插入的值比遍历的值小{parent = cur;cur = cur->_left;//往左走}else//说明插入的值已经存在,return false{return false;}}//走到这说明已经找到可以插入的地方 //创建一个新节点cur = new Node(kv);//判断插入的节点该连接到父节点的左还是右if (cur->_kv.first > parent->_kv.first)//cur 的值大于父节点的值,连右{parent->_right = cur;}else{parent->_left = cur;}//接下来就是判断平衡因子的时候了cur->_parent = parent;//若cur是此父节点的左子树(节点)那么父节点的平衡因子-- ,是右子树(节点)那么父节点平衡因子++//一直往上走直至parent为nullptr(根节点的父节点为空)或者平衡因子为0或者平衡因子为2(需要旋转)while (parent){if (cur == parent->_left)//cur在父左 父bf--{parent->_bf--;}else//cur在父右 父bf++{parent->_bf++;}if (parent->_bf == 0)//父bf==0 break{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父bf为1 or -1继续往上{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//平衡因子为2 只能旋转了//右高左低 左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1)//左高右低 右单旋{RotateR(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//两边都高左右双旋{RotateLR(parent);}else//右左双旋{RotateRL(parent);}}else{assert(false);}}return true;}

3.2旋转

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

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

void RotateL(Node* parent)//左单旋{//sub是parent ,subR是parent的右节点,subRL是subR的左节点Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//父节点的右节点指向subRL//subRL可能为空节点,也就是这颗树(子树)只有sub(parent节点)、subR、以及新增节点这三个节点 如果subRL为空,就不用将其父节点指向sub了if (subRL){subRL->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = subR;//把父节点的父节点指向subR//父节点(sub)有可能是一颗树(子树)的根节点 //如果(sub)是一颗树的根节点的话 subR直接为根节点,subR的parent直接是空if (parent = _root){subR = _root;subR->_parent = nullptr;}else//sub是一颗子树的根节点 {//得看sub是其父节点的左节点还是右节点//然后subR连接sub的父节点if (parent == ppnode->_left){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}//sub和subR平衡因子置为0subR->_bf = 0;parent->_bf = 0;}

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

//右单旋思路和左单旋差不多void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (parent == _root){subL = _root;subL->_parent = nullptr;}else{if (parent == ppnode->_left){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = 0;parent->_bf = 0;}

左右双旋—— 新节点插入较高左子树的右侧---左右(先左单旋再右单旋)

左右两边都高单旋解决不了问题

//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//subLR的平衡因子不同时对其它节点的平衡因子的改变也不同int bf = subLR->_bf;RotateL(parent->_left);//先走左单旋RotateR(parent);//再走右单旋if (bf == -1)//若subLR的bf为-1,则新增节点是subLR的左子树{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1)//若subLR的bf为1,则新增节点是subLR的右子树{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0) //若subLR的bf为0,那么subLR其本身就是新增节点{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

右左双旋——新节点插入较高右子树的左侧---右左(先右单旋再左单旋)

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

 4.判断是否为AVL树

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

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

	void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << "[" << root->_bf << "]" << endl;_Inorder(root->_right);}void Inorder(){_Inorder(_root);}

2. 验证其为平衡树 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确

	int _Height(Node* root){if (root == nullptr)return;int leftHight = Height(root->_left);int rightHight = Height(root->_right);return leftHight > rightHight ? leftHight + 1 : rightHight + 1;}int Height(){return _Height(_root);}bool _IsAVLTree(Node* root)
{if (root == nullptr){return true;}int leftHeight = Height(root->_left);int	rightHeight = Height(root->_right);if (abs(leftHeight - rightHeight) >= 2){cout << "不平衡" << endl;return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return _IsAVLTree(root->_left)&&_IsAVLTree(root->_right);
}bool IsAVLTree(){return _IsAVLTree(_root);}

如果走前序递归的话计算高度和前序递归会存在大量重复,所以还是走后序的同时求高度

后序先走左子树判断平衡返回高度;再走右子树判断平衡返回高度

bool _IsAVLTree(Node* root, int& height){if (root == nullptr){height = 0;return true;}//走个后序int leftHeight, rightHeight = 0;if (!_IsAVLTree(root->_left, leftHeight) || !_IsAVLTree(root->_right, rightHeight)){return false;}if (abs(leftHeight - rightHeight) >= 2){cout << "不平衡" << endl;return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;}bool IsAVLTree(){int height = 0;return _IsAVLTree(_root, height);}

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

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

相关文章

康奈尔开源近10万份审稿意见,未来论文发表或将由AI定夺

大语言模型&#xff08;LLMs&#xff09;的进步为自动化论文评审开辟了新途径&#xff0c;这些模型在学术反馈领域展现出巨大潜力。自动化评审的核心优势在于其能够精准指出论文草稿的不足之处&#xff0c;助力作者优化研究。尽管已有丰富的同行评审数据&#xff0c;但现有自动…

20.2 nginx

20.2 nginx 1. 学习目标2. 介绍2.1 正向代理2.2 反向代理2.3 动态静态资源分离2.4 nginx优缺点3. 安装3.1 Linux安装****************************************************************************************************************************************************…

基于stm32的流水灯设计

1基于stm32的流水灯设计[proteus仿真] 速度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的自行车测速系统设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&#xff0c…

《领导的气场——8堂课讲透中国式领导智慧》读书笔记

整体感悟 个人感觉书籍比较偏说教、理论&#xff0c;没有看完。 现仅仅摘录自己“心有戚戚焉”的内容。 经典摘录 管理的本质是通过别人完成任务。有一百件事情&#xff0c;一个人都做了&#xff0c;那只能叫勤劳&#xff1b;有一百件事情&#xff0c;主事的人自己一件也不做&…

js 获取浏览器相关的宽高尺寸

window 屏幕 屏幕分辨率的高&#xff1a; window.screen.height 屏幕分辨率的宽&#xff1a; window.screen.width 屏幕可用工作区高度&#xff1a; window.screen.availHeight 屏幕可用工作区宽度&#xff1a; window.screen.availWidth document 网页 网页可见区域宽&#xf…

C语言学习--练习4(二维数组)

目录 1.统计有序数组中的负数 2.矩阵对角线元素和 3.最富有客户的资产总量 4.托普利兹矩阵 5.矩阵中的幸运数 6.二进制矩阵中的特殊位置 7.岛屿的周长 1.统计有序数组中的负数 //直接遍历二维数组即可 int countNegatives(int** grid, int gridSize, int* gridColSize) …

比特币普通地址、隔离见证(兼容)、隔离见证(原生)、Taproot 地址傻傻分不清楚

我们在使用比特币钱包的时候&#xff0c;可以看到各种地址类型&#xff1a;普通地址、隔离见证&#xff08;兼容&#xff09;、隔离见证&#xff08;原生&#xff09;、Taproot 地址。 看得我们一脸懵逼&#xff0c;为什么会有这么多种类型的地址&#xff1f; 它们之间都有什么…

选修-单片机作业第1/2次

第一次作业 第二次作业 1、51 系列单片机片内由哪几个部分组成&#xff1f;各个部件的最主要功能是什么&#xff1f; 51系列单片机的内部主要由以下几个部分组成&#xff0c;每个部件的主要功能如下&#xff1a; 1. **中央处理器&#xff08;CPU&#xff09;**&#xff1a;这是…

装windows11+centos双系统时遇到问题及解决方法

从u盘启动提示linpus lite has been blocked 首先下载iso镜像文件&#xff0c;制作u盘启动工具&#xff0c;进行启动&#xff0c;这里进入bios界面进行启动项选择后&#xff0c;虽然已经将usb作为首要启动值 却会出现上图所示被“block”情形 需要在bios界面security选项&…

【数理统计实验(四)】方差分析

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

【动态规划】代码随想录算法训练营第四十四天 |完全背包,518. 零钱兑换 II , 377. 组合总和 Ⅳ (待补充)

完全背包理论基础 完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和…

三、实战篇 优惠券秒杀

源码仓库地址&#xff1a;gitgitee.com:chuangchuang-liu/hm-dingping.git 1、全局唯一ID 数据库默认自增的存在的问题&#xff1a; id增长规律明显受单表数据量的限制 场景一分析&#xff1a;id如果增长规律归于明显&#xff0c;容易被用户或者商业对手猜测出一些敏感信息&…

QT画图功能

QT画图功能 每个QWidget都自带的功能&#xff0c;继承了QPainteDevice都可以使用QPainter来进行绘图。 画图需要调用paintEvent绘制事件&#xff0c;paintEvent事件时QWidget类自带的事件。 重写paintEvent事件。&#xff08;重写事件&#xff1a;如果父类有某个方法&#xff…

Spring Boot 面试题及答案整理,最新面试题

Spring Boot中的自动配置是如何工作的&#xff1f; Spring Boot的自动配置是其核心特性之一&#xff0c;它通过以下方式工作&#xff1a; 1、EnableAutoConfiguration注解&#xff1a; 这个注解告诉Spring Boot开始查找添加了Configuration注解的类&#xff0c;并自动配置它们…

22.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-加载配置文件到分析工具界面

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;21.配置数据保存…

加快代码审查的 7 个最佳实践

目录 前言 1-保持小的拉取请求 2-使用拉取请求模板 3-实施响应时间 SLA 4-培训初级和中级工程师 5-设置持续集成管道 6-使用拉取请求审查应用程序 7-生成图表以可视化您的代码更改 前言 代码审查可能会很痛苦软件工程师经常抱怨审查过程缓慢&#xff0c;延迟下游任务&…

什么是GoogLeNet,亮点是什么,为什么是这个结构?

GooLeNet 亮点 最明显的亮点就是引入了Inception&#xff0c;初衷是多卷积核增加特征的多样性&#xff0c;提高泛化能力 &#xff0c;比如&#xff0c;最下边是一个输入层&#xff0c;然后这个输入分别传递给1*1&#xff0c;3 * 3 &#xff0c;5 * 5和一个最大池化层&#xff…

2024春招和暑期实习全面启动!

大家好&#xff0c;我是小柠檬。2024春招和暑期实习全面启动&#xff01;最近&#xff0c;我注意到很多同学都在积极投递简历。 3D视觉求职星球 今天给大家推荐我们的3D视觉岗求职星球&#xff0c;里面时常发布大量3D视觉岗位和星球专属内推岗位。 篇幅有限&#xff0c;文节选…

最新全流程GMS地下水数值模拟及溶质(包含反应性溶质)运移模拟技术深度应用

本文以地下水数值模拟软件GMS操作&#xff0c;本文中强调模块化教学&#xff0c;分为前期数据收集与处理&#xff1b;三维地质结构建模&#xff1b;地下水流动模型构建&#xff1b;地下水溶质运移模型构建和反应性溶质运移构建5个模块&#xff1b;采用全流程模式将地下水数值模…

后端项目访问不了

问题&#xff1a; 后端启动不了&#xff0c;无法访问网站 原因&#xff1a; 1.防火墙没有关 2.有缓存 3、项目没有启动 4、docker没有启动 解决&#xff1a; 先查看进程&#xff1a;docker ps&#xff0c;必须有三个 详细查看&#xff1a;docker ps -a exited代表没有开启…