<C++> AVLTree

目录

1. AVL概念

2. AVL树节点的定义

3. AVL树的插入

4. AVL树的旋转

5. AVL树的验证

6. AVL树的删除

7. AVL树的性能

暴力搜索、二分搜索、二叉搜索树、二叉平衡搜索树(AVL、红黑树)、多叉平衡搜索树(B树)、哈希表 

1. AVL概念

        二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

        因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差 (简称平衡因子的绝对值不超过1 (-1 / 0 / 1) ,不是绝对的0是因为做不到绝对的平衡(例如,非满二叉树)

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

        如果一棵二叉搜索树是高度平衡的,它就是AVL 树。如果它有 n 个结点,其高度可保持在
,搜索时 间复杂度 O( log N  )
        
        满二叉树:2^h - 1 = N
        
                                                        -> 约等于logN        
        
        AVL树:2^h - X = N        (X范围 [ 1, 2^(h - 1) - 1 ] 

2. AVL树节点的定义

        直接写key—value版本,因为它与单key版本,只是多了一个对应value(pair就解决了),可以看二叉搜索树一文再理解两个版本的关系

        加入平衡因子会更加方便

template<class K, class V>
struct AVLTreeNode
{int _bf;    //balance factorpair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

3. AVL树的插入

        AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式找到合适点插入新节点

  2. 调整节点的平衡因子

  • 新增在左边,parent平衡因子--
  • 新增在右边,parent平衡因子++
  • 如果插入后,节点的平衡因子变成了0,那么说明这个节点的左右子树高度平衡了,高度没有增大或减小,所以不会对往上的祖先产生影响,就不需要往上更新祖先的平衡因子
  • 如果插入后,节点的平衡因子变成了1 或 -1,那么说明这个节点的左右子树高度不平衡,高度增大了或减小了,会对其祖先的平衡因子产生影响,所以使用 parent 指针向上更新平衡因子(这就体现了 parent 指针的用武之地),直到某一个祖先的平衡因子变为 0 或根节点就停止!!!(因为如果一个节点的高度没变,即平衡因子为0,那么它就不影响parent节点)
  • 如果插入后,节点的平衡因子变成了2 或 -2,说明parent所在的子树的高度变化且不平衡了!这时就要体现 AVL 平衡的特点,对 parent 所在子树进行旋转
  • 不会出现3 或 -3以上情况,因为它们必然会经历2 或 -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 (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (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 // if (cur == parent->_right){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){// 子树不平衡了,需要旋转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){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else  //正常代码不会执行到这里,所以抛异常{assert(false);}}return true;}

4. AVL树的旋转

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

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

        cur 的 left 链接到 parent 的 right,再将 parent 链接到 cur 的 left

        在操作时,不仅要更新_bf平衡因子,还要额外注意父节点指针,一不留心就会出现三叉链,而且还要注意,parent 是不是 root,如果不是根节点,那么还要提前记录 parent 的 parent,以便使cur 的 parent 指向修改

符合左单旋的场景无穷尽,下图直接概括了左单旋的抽象总结图: 

         但是无论h高度是多少,节点的操作是一样的

//左单旋void RotateL(Node* parent){++_rotateCount;Node* cur = parent->_right;Node* curleft = cur->_left;//开始链,注意父亲链parent->_right = curleft;if (curleft)	//这里判断cur的right是不是为空,为空的话就不能使用指针链接{curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;//判断parent的父亲的情况if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}//记得父亲链cur->_parent = ppnode;}//不要忘了平衡因子parent->_bf = cur->_bf = 0;}

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

        右单旋与左单旋完全对称

简单分析后即可总结出上图:

	//右单旋void RotateR(Node* parent){++_rotateCount;Node* cur = parent->_left;Node* curright = cur->_right;//开始链,注意父亲链parent->_left = curright;if (curright)	//这里判断cur的right是不是为空,为空的话就不能使用指针链接{curright->_parent = parent;}Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;//判断parent的父亲的情况if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}//记得父亲链cur->_parent = ppnode;}//不要忘了平衡因子parent->_bf = cur->_bf = 0;}

新节点插入较高右子树的左侧--- 右左:先右单旋再左单旋
     上面的两种情况,都是极端的左或右,新增节点与祖先处于一条直线。而双旋的情况更复杂,新增节点与祖先节点的连线是一条折线
双旋本质相当于第一次的旋转是预处理:将树修正为左单旋情况或右单旋情况,第二次就是简单的单旋情况
       
        那么只需复用上面的左右单旋函数即可解决问题,但是复用两个函数后,30、90、60这三个节点的平衡因子都会成为0!这是不正确的,因为h大小分情况,并且在60的左右插入位置不同,导致结果的平衡因子也不同。
分类讨论:
所以对于不同情况,要进行分类的处理平衡因子
	//右左双旋void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);//分情况更新平衡因子if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}}

        代码解析:在插入一个节点之后,向上更新平衡因子,直到遇见以上四种情况,开始旋转处理

新节点插入较高左子树的右侧 --- 左右:先左单旋再右单旋
        同理
	//左右双旋void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);//分情况更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}}
        如果程序出错了,数据量很大调试就很难受了,这时候可以一段一段注释调试,对于不同场景可能不合适,所以还可以手写一个判断函数,让计算机协助你找到错误,打开监视窗口、打断点,要带着思路、有预测结果的去调试,中间可以使用条件断点(可以手写if,里面写一些语句,程序走到这里时停下)

总结:

        假如以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树的验证

	//递归求高度,测试代码是否正确int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}//测试代码是否正确,是不是AVL结构bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;int leftHight = Height(root->_left);int rightHight = Height(root->_right);if (rightHight - leftHight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHight - leftHight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);}

我们使用随机生成的数来测试 

#include "AVLTree.h"
#include <vector>const int N = 1000000;int main()
{AVLTree<int, int> at;vector<int> v;v.reserve(N);srand(time(0));for (int i = 0; i < N; i++){v.push_back(rand());}for (auto e : v){at.Insert(make_pair(e, e));}cout << at.IsBalance() << endl;return 0;
}

6. AVL树的删除

        按搜索树的规则查找节点进行删除,与插入时对节点的平衡因子更新相反,如果删除节点之后 parent 平衡因子为0,那么需要继续往上更新,如果是 1 或 -1,那么不需要往上更新

        而且删除时,双旋情况更多,平衡因子的更新更复杂

7. AVL树的性能

        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 logN 但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

        因此,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

通俗易懂理解Java泛型

什么是泛型 参数化类型 Java泛型是J2 SE1.5中引入的一个新特性&#xff0c;其本质是参数化类型&#xff0c;也就是说所操作的数据类型被指定为一个参数&#xff08;type parameter&#xff09;这种参数类型可以用在类、接口和方法的创建中&#xff0c;分别称为泛型类、泛型接口…

c++ websocket简单讲解

只做简单讲解。 一.定义和原理 WebSocket 是从 HTML5 开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的消息推送机制&#xff0c;传统的 web 程序都是属于 "⼀问⼀答" 的形式&#xff0c;即客⼾端给服务器发送了⼀个 HTTP 请求&#xff0c;服务器给客⼾端返回⼀个 HTTP 响…

JS【详解】内置构造函数/对象 URL(可用于创建和解析 URL,操作搜索参数,编码 url)

用途 用于创建和解析 URL 语法 new URL(url, [base])第1个参数 url &#xff1a;可为完整 url 或路径&#xff08;如果设置了 base&#xff09;第2个参数 base&#xff1a;可选的 base URL&#xff0c;若第1个参数是路径&#xff0c;则会根据这个 base 生成 URL new URL(https…

【内网渗透】最保姆级的春秋云镜Certify打靶笔记

目录 flag1 flag2 flag3 flag4 flag1 fscan扫外网 访问8983端口&#xff0c;发现配置项有log4j 【vulhub】Log4j2&#xff1a;CVE-2021-44228漏洞复现_vulhub cve-2021-44228-CSDN博客 /solr/admin/collections?action${jndi:ldap://z5o5w8.dnslog.cn} dnslog测出可…

C++string类相关OJ练习(2)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Cstring类相关OJ练习(2) 收录于专栏【C语法基础】 本专栏旨在分享学习C的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1.反转字符串 …

windows安装composer

windows安装composer 1.介绍 Composer 是一个用于PHP的依赖管理工具。Composer允许你声明你的PHP项目所依赖的库&#xff0c;并管理它们。它会安装和更新你项目所需要的库。 Composer为我们的项目提供了两个功能&#xff1a; 帮助PHP进行依赖管理。在项目中实现文件自动加载…

紫光同创——PLL IP 的使用(Logos2)

本文档主要针对 Logos2 系列的 PLL 配置&#xff0c;至于 Logos 系列的 PLL&#xff0c;可以参考《PLLIP 的使用(Logos)》的文档。 一、PLL IP 介绍 1、PLL 基本配置模式 Basic Configurations PLL IP 是紫光同创基于 PLL 及时钟网络资源设计的 IP&#xff0c;通过不同的参数配…

电量提醒工具(充电提醒)

可提醒50%到100%之间的整电量&#xff0c;女声提醒&#xff0c;不占资源&#xff0c;防止设备未充上电。 https://download.csdn.net/download/zzmzzff/89713499

input系统之InputDispatcher

往期文章&#xff1a; Input系统之IMS的启动流程 input系统之InputReader 1.概述 InputReader和InputDispatcher是Input系统的重要组成部分&#xff0c;InputReader主要负责从设备节点获取原始输入事件&#xff0c;并将封装好的事件交给InputDispatcher&#xff1b;InputDis…

快速构建 AI 应用的利器:Python 库 Mesop

在当今这个 AI 技术飞速发展的时代&#xff0c;开发者们总是希望能够更快、更便捷地构建 AI 应用程序。今天&#xff0c;我要给大家介绍一个由 Google 推出的 Python 库——Mesop。它的出现&#xff0c;让我们能够轻松地搭建高效的 AI 应用。 Mesop 是什么&#xff1f; Mesop …

MATLAB生成COE文件

MATLAB代码 % 参数设置 N 4096; % 数据点数量 t linspace(0, 2*pi, N); % 时间向量 width 12; % 位宽% 正弦波&#xff0c;幅度在0到5之间 sine_wave 2.5 * sin(t) 2.5;% 三角波&#xff0c;幅度在0到5之间 tri_wave 5 * (1 - abs(mod(t/(2*pi)*4, 2) - 1));% 方波&…

echarts--Y轴名称超宽换行显示行高问题处理

设置yAxis.axisLabel.overflow:break为超宽换行 yAxis: [{type: category,inverse: true, //y轴坐标轴向下position: left, // 设置 y 轴的位置在左边offset: 65, // 设置 y 轴距离左边的偏移量axisLine: {show: false,},axisTick: {show: false},axisLabel: {show: true,inter…

集成电路学习:什么是MOSFET(MOS管)

一、MOSFET&#xff1a;MOS管 MOSFET&#xff0c;全称Metal-Oxide-Semiconductor Field-Effect Transistor&#xff0c;即金属-氧化物半导体场效应晶体管&#xff0c;也常被称为MOS管或金氧半场效晶体管。它是一种可以广泛使用在模拟电路与数字电路的场效应晶体管&#xff08;f…

day07-集合-MapStream递归

一、Collections 1.1 可变参数 可变参数就是一种特殊形参&#xff0c;定义在方法、构造器的形参列表里&#xff0c;格式是&#xff1a;数据类型... 参数名称 ​ 优点特点&#xff1a;可以不传数据给它&#xff1b;可以传一个或者同时传多个数据给它&#xff1b;也可以传一个数…

【系统架构设计师-2022年】综合知识-答案及详解

文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6~7题】【第8题】【第9题】【第10题】【第11~12题】【第13题】【第14题】【第15题】【第16题】【第17~18题】【第19题】【第20题】【第21题】【第22题】【第23题】【第24题】【第25题】【第26题】【第27题】【第28题…

【AQS源码】深入理解AQS的工作原理

【AQS源码】深入理解AQS的工作原理-CSDN博客

叉车专用AI防撞预警系统,带行车记录功能,守护人车安全!

AI防撞预警系统是一款为工业车辆等工程设备专门设计的智能视频监控装置。该系统通过三个独立的摄像头和深度学习算法实现机器视觉识别。 本系统的一个重要功能是能够能够实时侦测工作区域的危险状态并提供警示&#xff0c;一旦有人员进入危险区域&#xff0c;驾驶员即可得到动态…

SpringBoot2:RESTFUL风格接口开发及源码解读

一、RESTFUL简介 Rest风格支持&#xff08;使用HTTP请求方式&#xff0c;动词来表示对资源的操作&#xff09; 以前&#xff1a;/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户 现在&#xff1a; /user GET-获取用户 DELETE-删除用户 PUT-修改…

海外合规|新加坡网络安全认证计划简介(三)-Cyber Trust

一、 认证简介&#xff1a; Cyber Trust标志是针对数字化业务运营更为广泛的组织的网络安全认证。该标志针对的是规模较大或数字化程度较高的组织&#xff0c;因为这些组织可能具有更高的风险水平&#xff0c;需要他们投资专业知识和资源来管理和保护其 IT 基础设施和系统。Cy…

ES6语法详解

以下是ES6常用的一些语法和特性&#xff1a; 声明变量的关键字变化&#xff1a;使用let和const、var来声明变量。 箭头函数&#xff1a;使用箭头&#xff08;>&#xff09;定义函数&#xff0c;简化函数的写法。 模板字符串&#xff1a;使用反引号&#xff08;&#xff0…