会旋转的树,你见过吗?

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻强烈推荐优质专栏: 🍔🍟🌯C++的世界(持续更新中)
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:AVL树的实现,旋转的详细分析,抽象图,具体图分步骤讲解,保姆级教学。
金句分享:
✨人生若只如初见,✨
✨何事秋风悲画扇。✨

前言

前面我们学习了二叉搜索树,二叉搜索树如果左右子树高度相差不大,那么效率还是可观的,比如:满二叉搜索树的查询效率为 O(logn).
但是,如果插入的数据是有序的,或者大部分有序,则会导致 “二叉搜索树” 退化为类似于链表的结构.
链表查询数据的时间复杂度牛牛就不用多说了吧.答案: O(n)

示例:
在这里插入图片描述

目录

  • 前言
  • 一、AVL树的介绍
  • 二、AVL树的模拟实现
    • 🍭结点类
    • 🍔AVL树的框架:
    • 2.1 "插入"操作(重点)
      • ①:右旋
        • (1) 右旋具体图:
        • (2)右旋抽象图:
      • ②左旋:
        • (1)左旋具体图:
        • (2)左旋抽象图
      • ③右左双旋
        • (1)右左双旋具体图
        • (2)右左双旋抽象图
      • ④左右双旋
        • (1)左右双旋具体图
        • (2)左右双旋抽象图
      • "插入"操作的代码实现:
    • 2.2 中序遍历:
    • 2.3 AVL树的"高度"
    • 2.4 验证AVL树
  • 结语

一、AVL树的介绍

AVL树是一种自"平衡二叉搜索树",它的每个节点存储一个关键字,具有以下特点:

  1. 每个节点的左右子树高度差至多为1,也就是说,AVL树的任何一个节点的左右子树高度差不超过1。

  2. 对于任意一个节点,其左子树和右子树都是一个AVL树。

  3. AVL树中的每个节点都能保证左子树中的所有节点小于当前节点的关键字,右子树中的所有节点大于当前节点的关键字。(也就是满足二叉搜索树的性质)

如果我们定义 平衡因子=右子树的高度-左子树的高度
则树中每个结点的平衡因子的绝对值 不超过1 即可以为( 1 0 -1三种)
1:表示右子树比左子树高.
0:表示左子树和右子树一样高.
-1:表示左子树比右子树高.

每当向AVL树中插入、删除节点时,AVL树会自动地进行旋转操作将树变为平衡状态,从而保证了AVL树的平衡性。

在这里插入图片描述
会旋转的树才够强,AVL树的查询数据的时间复杂度总是控制在 O(logn)量级.

二、AVL树的模拟实现

补充知识点:
c++pair类是一个模板类,用于将两个值组成一个单元,也就是我们称为的键值对.

template<class T1, class T2>
struct pair {typedef T1 first_type;typedef T2 second_type;T1 first;  // 第一个元素T2 second; // 第二个元素// 默认构造函数pair() : first(T1()), second(T2()) {}// 初始化构造函数pair(const T1& x, const T2& y) : first(x), second(y) {}
};

🍭结点类

没错,AVL树是三叉树,为了方便旋转,我们需要多存储一个指针域(_parent) ,指向父节点.

template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;							//键值对AVLTreeNode<K, V>* _left;				//左子树AVLTreeNode<K, V>* _right;				//右子树AVLTreeNode<K, V>* _parent;				//父节点int _bf;								// balance factor平衡因子//构造AVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};

🍔AVL树的框架:

// AVL树的结构
template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K,V> Node;		
public:AVLTree(): _root(nullptr){}// 在AVL树中插入值为data的节点bool Insert(const pair<K, V>& kv);void  InOrder();// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_root);}private:// 根据AVL树的概念验证proot是否为有效的AVL树bool _IsAVLTree(Node* root);size_t _Height(Node* root);void  _InOrder(Node* root);// 右单旋void RotateR(Node* parent);// 左单旋void RotateL(Node* parent);// 右左双旋void RotateRL(Node* parent);// 左右双旋void RotateLR(Node* parent);private:Node* _root;
};

2.1 "插入"操作(重点)

注意: 本篇实现的AVL树,平衡因子是右子树高度—左子树高度.

故:
当新增结点在父亲结点的左边时,左子树的高度+1,则平衡因子-1.
当新增结点在父亲结点的右边时,右子树的高度+1,则平衡因子+1.
在这里插入图片描述
子树的平衡因子变化,可能会影响祖先路径上的结点,需要继续向上更新.

(1) 当新增结点后,父节点的平衡因子变成0,则插入结束.
在这里插入图片描述
(2) 当新增结点后,父节点的平衡因子变成1或者-1,则需要向上更新.

在这里插入图片描述
(3)当新增结点后,父节点的平衡因子变成2或者-2需要旋转.

①:右旋

当继续向上更新到父节点是平衡因子-2时,则需要右旋.
因为左边比右边高,需要旋转到右边.使其平衡.

(1) 右旋具体图:

在这里插入图片描述

关键步骤:

使cur成为新的父节点
cur的右孩子,成为parent的左孩子
parent成为cur的右孩子
在这里插入图片描述

(2)右旋抽象图:

在这里插入图片描述

更新平衡因子:
从抽象图可以看出,右旋旋转后,平衡因子cur parent都可以无脑设置为0.

代码实现:

//右旋
template<class K,class V>
void AVLTree<K,V>::RotateR(Node* parent)
{Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* cur_right = cur->_right;//cur的右子树变成parent的左子树if (cur_right) {					//cur_right  可能不存在parent->_left = cur_right;cur_right->_parent = parent;//保持三叉连}else {parent->_left = nullptr;}//parent变成cur的右子树cur->_right = parent;		parent->_parent = cur;//保持三叉连if (ppnode) {		cur->_parent = ppnode;if (ppnode->_left == parent) {		//如果是上面的左子树ppnode->_left = cur;}else {ppnode->_right = cur;			//如果是上面的右子树}}else {									//如果ppnode是nullptr,则代表parent为root_root = cur;cur->_parent = nullptr;}parent->_bf = 0;cur->_bf = 0;
}

②左旋:

当继续向上更新到父节点平衡因子是2时,则需要左旋.
因为右边比左边高,需要旋转到左边,使其平衡.

(1)左旋具体图:

在这里插入图片描述
关键步骤:

使cur成为新的父节点
cur的左孩子,成为parent的右孩子
parent成为cur的左孩子
在这里插入图片描述

(2)左旋抽象图

在这里插入图片描述

更新平衡因子:
从抽象图可以看出,左旋旋转后,平衡因子cur parent都可以无脑设置为0.

代码实现:

//左旋
template<class K, class V>
void AVLTree<K, V>::RotateL(Node* parent)
{Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* cur_left = cur->_left;//cur的右子树变成parent的左子树if (cur_left) {					//cur_right  可能不存在parent->_right = cur_left;cur_left->_parent = parent;//保持三叉连}else {parent->_right = nullptr;}//parent变成cur的左子树cur->_left = parent;parent->_parent = cur;//保持三叉连if (ppnode) {cur->_parent = ppnode;if (ppnode->_left == parent) {		//如果是上面的左子树ppnode->_left = cur;}else {ppnode->_right = cur;			//如果是上面的右子树}}else {									//如果ppnode是nullptr,则代表parent为root_root = cur;cur->_parent = nullptr;}//更新平衡因子parent->_bf = 0;cur->_bf = 0;
}

③右左双旋

cur->_bf 的值是-1,并且parent->_bf 的值是 2,如下图15结点,22结点,18结点这般成折线状。
在这里插入图片描述

(1)右左双旋具体图

在这里插入图片描述

(2)右左双旋抽象图

在这里插入图片描述

对于双旋,重点在于如何更新平衡因子。

双旋的重点!!!:
在这里插入图片描述

代码实现:

// 右左双旋template<class K, class V>
void AVLTree<K, V>::RotateRL(Node* parent) {Node* cur = parent->_right;Node* cur_left = cur->_left;RotateR(cur);			//以parent的right作为新的parent进行右单旋RotateL(parent);//更新平衡因子(最重要)if (cur_left->_bf == 0) {		//情况1parent->_bf = 0;cur_left->_bf = 0;cur->_bf = 0;}else if(cur_left->_bf == 1) {	//情况3parent->_bf = -1;cur_left->_bf = 0;cur->_bf = 0;}else if (cur_left->_bf == -1) {	//情况2parent->_bf = 0;cur_left->_bf = 0;cur->_bf = 1;}else {assert(false);}
}

④左右双旋

与左右双旋类似,这里不过多介绍了,注意更新平衡因子!!!

(1)左右双旋具体图

在这里插入图片描述

(2)左右双旋抽象图

在这里插入图片描述

template<class K, class V>
void AVLTree<K, V>::RotateLR(Node* parent) {Node* cur = parent->_left;Node* cur_right = cur->_right;RotateR(cur);			//以parent的right作为新的parent进行右单旋RotateL(parent);//更新平衡因子(最重要)if (cur_right->_bf == 0) {parent->_bf = 0;cur_right->_bf = 0;cur->_bf = 0;}else if (cur_right->_bf == 1) {parent->_bf = 0;cur_right->_bf = 0;cur->_bf = -1;}else if (cur_right->_bf == -1) {parent->_bf = 1;cur_right->_bf = 0;cur->_bf = 0;}else {assert(false);}
}

"插入"操作的代码实现:

template<class K,class V>
bool AVLTree<K,V>::Insert(const pair<K, V>& kv)
{if (_root == nullptr) {_root = new Node(kv);return true;}Node* cur = _root, * parent = nullptr;//寻找插入位置while (cur) {parent = cur;if (_root->_kv.first > kv.first) {cur = cur->_left;}else if (_root->_kv.first < kv.first) {cur = cur->_right;}else {return false;}}//判断插入在左边还是右边cur = new Node(kv);if (kv.first < parent->_kv.first){				//插入在左边parent->_left = cur;}else{									//插入在右边parent->_right = cur;}cur->_parent = parent;					//保证三叉链的关系//更新平衡因子,最终可能更新到根节点while (parent) {							if (cur==parent->_left) {			//如果是插入在左边,平衡因子-1parent->_bf--;					}else if(parent->_right == cur) {								//如果是插入在右边,//平衡因子+1parent->_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 (cur->_bf == -1 && parent->_bf == -2) {RotateR(parent);}else 	if (cur->_bf == 1 && parent->_bf == 2) {	//右边高,左单旋RotateL(parent);}else 	if (cur->_bf == 1 && parent->_bf == -2) {	//左右双旋RotateLR(parent);}else 	if (cur->_bf == -1 && parent->_bf == 2) {	//右左双旋RotateRL(parent);}}}return true;
}

2.2 中序遍历:

由于中序遍历需要传参 参数为根节点,而根节点是私有成员变量,所以这里用再套一层函数的方法,是一个不错的设计。

template<class K, class V>
void  AVLTree<K, V>::_InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << "->" << root->_kv.second << endl;_InOrder(root->_right);
}template<class K, class V>
void  AVLTree<K, V>::InOrder()
{if (_root == nullptr){cout << "空树" << endl;return;}_InOrder(_root);
}

2.3 AVL树的"高度"

同求二叉树的高度没有啥区别。

需要注意的是:
1处和2处,需要记录左右子树的根否则后面的都需要重新大量的重复计算。


template<class K, class V>
size_t AVLTree<K, V>::_Height(Node* root)
{if (root == nullptr)return 0;int leftHeight = _Height(root->_left);		//1int rightHeight = _Height(root->_right);	//2return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

2.4 验证AVL树

判断每棵树的平衡因子是否=右子树-左子树的高度即可。

template<class K, class V>
bool AVLTree<K, V>::_IsAVLTree(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&& _IsAVLTree(root->_left)&& _IsAVLTree(root->_right);
}

结语

AVL树的平衡性使得它的插入、删除和查找操作的时间复杂度都是O(logn),与红黑树相当。然而,由于AVL树在每次插入或删除时都需要进行平衡调整,所以它的常数比红黑树稍大,因此在实际应用中,红黑树更为常用。
后续会更新红黑树的介绍,很多人认为红黑树是比AVL树还要优秀的结构,不想要了解一下吗? 还请保持关注哦!
在这里插入图片描述

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

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

相关文章

第二证券:激发资本市场数智新动能 实现高质量发展

12月15日至16日&#xff0c;深交所与港交所、广期所联合举行主题为“科技引领数智赋能”的2023年大湾区生意所科技大会。 本次大会深化贯彻落实中心经济作业会议精神和中心金融作业会议精神&#xff0c;聚焦工作数字化转型和科技立异前沿趋势&#xff0c;深化粤港澳大湾区协同…

使用QGIS快速制作三维地形图

使用QGIS快速制作三维地形图 使用QGIS快速构建任意地区三维可视化地形效果图及其他。 使用插件 Qgis2threejs 三维可视化地形图 QuickMapServices 在线DOM影像地图加载及下载 OpenTopography DEM Downloader 地形图下载 QuickOSM 下载OSM数据。 步骤 制作区域矢…

7.26 SpringBoot项目实战【还书】

文章目录 前言一、编写控制器二、编写服务层三、Git提交前言 本文是项目实战 业务接口 的最后一篇,上文 曾说过【还书】的 入口是【我的借阅记录】,因为【还书】是基于一次借阅记录而言,另外在4.2 数据库设计 曾分析过【还书】的业务场景,需要执行两步操作: 更新【借阅记…

中文文章自动润色 神码ai

大家好&#xff0c;今天来聊聊中文文章自动润色&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;中文文章自动润色&#xff1a;提升文本质量的利器 一、引…

Mysql高可用|索引|事务 | 调优

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 文章目录 前言sql语句的执行顺序关键词连接名字解释sql语句 面试坑点存储引擎MYSQL存储引擎 SQL优化索引索引失效索引的数据结构面试坑点 锁事务四大特性事务的隔离级别MVCC 读写分离面试坑…

minio异常处理:S3 API Requests must be made to API port

1、创建minio服务时候需要映射出console端口和api端口&#xff0c;指定console端口和api端口 docker run -p 9000:9000 -p 9099:9099 --name minio -d --restartalways -e "MINIO_ACCESS_KEYadmin" -e "MINIO_SECRET_KEYMINIOE:<&G5*;dL?(fr" -v…

从零开始创建一个项目,springBoot+mybatisPlus+mysql+swagger+maven

一&#xff0c;前提 从零开始创建一个项目&#xff0c;绑定了数据库 用到的技术栈&#xff1a;springBootmybatisPlusmysqlswaggermaven 二&#xff0c;创建项目步骤 1&#xff0c;创建项目 创建出来的项目结构如图所示 2&#xff0c;修改配置文件 因为我比较习惯yml语言&…

高性价比AWS Lambda无服务体验

前言 之前听到一个讲座说到AWS Lambda服务&#xff0c;基于Serverless无服务模型&#xff0c;另外官网还免费提供 100 万个请求 按月&#xff0c;包含在 AWS 免费套餐中是真的很香&#xff0c;对于一些小型的起步的网站或者用户量不大的网站&#xff0c;简直就是免费&#xff…

SEO专业人士成功所需的8大技能

你有能力在SEO领域建立职业生涯吗&#xff1f;您需要某些技能才能成功。在这里了解这些技能是什么。 尽管SEO已经存在了几十年&#xff0c;但许多大学仍然没有教授SEO&#xff0c;也没有在大多数营销课程中提及。 SEO专业人士来自不同的背景。有些是程序员&#xff0c;有些是…

window10设置定时关机

进入“启动程序”项&#xff0c;如果系统在C盘&#xff0c;那么在“程序或脚本”框内输入“C&#xff1a;\Windows\system32\shutdown.exe”&#xff08;注意根据个人实际情况修改盘符&#xff09;&#xff1b;然后在“参数”处填入“-s -t 0”&#xff08;即立即关机&#xff…

css实现边框彩虹跑马灯效果

效果展示 代码实战 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-…

记录一次API报文替换点滴

1. 需求 各位盆友在日常开发中&#xff0c;有没有遇到上游接口突然不合作了&#xff0c;临时需要切换其他接口的情况&#xff1f;这不巧了&#xff0c;博主团队近期遇到了&#xff0c;又尴尬又忐忑。 尴尬的是临时通知不合作了&#xff0c;事前没有任何提醒&#xff1b; 忐忑…