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

news2024/10/6 7:02:40

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

          主厨:邪王真眼

 主厨的主页: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=nullptr
		while (cur)
		{
			parent = cur;
			if (cur->_val > val)
				cur = cur->left;
			else if (cur->_val < val)
				cur = cur->_right;
			else
				return 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, 1
		while (parent)//向上回溯检测平衡因子
		{
//, 插入则分以下两种情况:
 //1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
 //2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
			if (parent->_left == cur)
				parent->_bf--;
			else
				parent->_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树结构,则直接断言报错
			else
				assert(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;
		else
			grandparent->_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;
		else
			grandparent->_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.coloradmin.cn/o/1566211.html

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

相关文章

每日面经分享(Spring Boot: part3 Service层)

SpringBoot Service层的作用 a. 封装业务逻辑&#xff1a;Service层负责封装应用程序的业务逻辑。Service层是控制器&#xff08;Controller&#xff09;和数据访问对象&#xff08;DAO&#xff09;之间的中间层&#xff0c;负责处理业务规则和业务流程。通过将业务逻辑封装在S…

什么是智慧驿站?智慧驿站主要应用有哪些?新型智慧公厕解说

智慧驿站是一种融合了创意设计和多项功能的新型智慧公厕&#xff0c;它在信息化公共厕所的基础上&#xff0c;以创意的外观设计、全金属结构用材、快速制作整体运输、快速部署落地使用等价值特点&#xff0c;所打造了一个集购物、互动、休憩等多种功能于一体的城市基础设施。无…

【智能算法】金枪鱼群优化算法(TSO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.代码展示4.参考文献 1.背景 2021年&#xff0c;Xie等人受到自然界中金枪鱼狩猎行为启发&#xff0c;提出了金枪鱼优化算法&#xff08;Tuna swarm optimization&#xff0c;TSO&#xff09;。 2.算法原理 2.1算法思想 TSO模…

Rust语言中Regex正则表达式,匹配和查找替换等

官方仓库&#xff1a;https://crates.io/crates/regex 文档地址&#xff1a;regex - Rust github仓库地址&#xff1a;GitHub - rust-lang/regex: An implementation of regular expressions for Rust. This implementation uses finite automata and guarantees linear tim…

LCD1602显示屏

LCD1602显示 概述 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) 引脚说明 //电源 VSS -- GND VDD -- 5V //对比度 VO -- GND //控制线 RS -- P1.0 RW -- P1.1 E -- P1.4 //背光灯 A -- 5…

Python快速入门系列-8(Python数据分析与可视化)

第八章:Python数据分析与可视化 8.1 数据处理与清洗8.1.1 数据加载与查看8.1.2 数据清洗与处理8.1.3 数据转换与整理8.2 数据可视化工具介绍8.2.1 Matplotlib8.2.2 Seaborn8.2.3 Plotly8.3 数据挖掘与机器学习简介8.3.1 Scikit-learn8.3.2 TensorFlow总结在本章中,我们将探讨…

在Java中对SQL进行常规操作的通用方法

SQL通用方法 一、常规方法增删改查二、具体优化步骤1.准备工作2.getcon()方法&#xff0c;获取数据库连接对象3.closeAll()方法&#xff0c;关闭所有资源4.通用的增删改方法5.通用的查询方法6.动态查询语句 总结 一、常规方法增删改查 在常规方法中&#xff0c;我们在Java中对…

rocketmq的运维

1. admintool创建topic的时候 -o 的用法含义 https://rocketmq.apache.org/zh/docs/4.x/producer/03message2/ 有关orderMessageEnable和returnOrderTopicConfigToBroker的设置可以参考 https://blog.csdn.net/sdaujsj1/article/details/115741572 -c configFile通过-c命令指…

Sy6 编辑器vi的应用(+shell脚本3例子)

实验环境&#xff1a; 宿主机为win11&#xff0c;网络&#xff1a;10.255.50.5 6389 WSL2 ubuntu 目标机的OS&#xff1a;Ubuntu 内核、版本如下&#xff1a; linuxpeggy0223:/$ uname -r 5.15.146.1-microsoft-standard-WSL2 linuxpeggy0223:/$ cat /proc/version Linux vers…

MYSQL数据库:告别慢查询,优化性能大揭秘

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MYSQL数据库&#xff1a;告别慢查询&#xff0c;优化性能大揭秘 文章目录 一、揭秘…

「每日跟读」句型公式 第2篇

「每日跟读」句型公式 第2篇 1. I’m thinking about____ 我在考虑____ I’m thinking about my future career (我正在思考我未来的职业) I’m thinking about our marriage (我在考虑我们的婚姻) I’m thinking about taking a vacation (我在考虑度一个假) I’m think…

书生·浦语大模型InternLM-Chat-1.8B 智能对话 Demo 第二期

文章目录 InternLM-Chat-1.8B 智能对话 Demo环境准备下载模型运行 InternLM-Chat-1.8B web 运行八戒 demo下载模型执行Demo InternLM-Chat-1.8B 智能对话 Demo 环境准备 在InternStudio平台中选择 10% A100(1/4) 的配置&#xff08;平台资源有限&#xff09;&#xff0c;如下图…

【微服务】——Nacos注册中心

这里写自定义目录标题 1.认识和安装Nacos2.服务注册到nacos1&#xff09;引入依赖2&#xff09;配置nacos地址3&#xff09;重启 3.服务分级存储模型3.1.给user-service配置集群3.2.同集群优先的负载均衡 4.权重配置5.环境隔离5.1.创建namespace5.2.给微服务配置namespace 6.Na…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(7)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 PS敬伟01——90集等文件 https://www.alipan.com/s…

Typecho自媒体博客Spimes主题 X7.2

主题介绍 spimes主题专为博客、自媒体、资讯类的网站设计开发&#xff0c;自适应兼容手机、平板设备。一款简约新闻自媒体类的 typecho 主题&#xff0c;设计上简约、干净、精致、响应式&#xff0c;后台设置更是强大而且实用的新闻自媒体类主题。 已经更新到7.2&#xff0c;删…

优于五大先进模型,浙江大学杜震洪团队提出 GNNWLR 模型:提升成矿预测准确性

卡塔尔世界杯自 2010 年荣膺举办权&#xff0c;直至 2022 年辉煌成功举办&#xff0c;累计投入资金高达约 2,290 亿美元。相较之下&#xff0c;此前七届世界杯的总花费仅约 400 多亿美元。这场体育盛事展现出奢华无度的风采&#xff0c;归根结底源于卡塔尔这个国度的深厚底蕴。…

官宣!一文掌握2024百度CreateAI开发者大会最新议程

4月16日上午9:00&#xff0c;以“创造未来”为主题的2024百度Create AI开发者大会将在深圳国际会展中心&#xff08;宝安&#xff09;开幕。此次大会将是近十年来&#xff0c;粤港澳大湾区规格最高的AI大会&#xff0c;将聚焦炙手可热的AI话题&#xff0c;在大会主论坛、分论坛…

回溯算法|46.全排列

力扣题目链接 class Solution { public:vector<vector<int>> result;vector<int> path;void backtracking (vector<int>& nums, vector<bool>& used) {// 此时说明找到了一组if (path.size() nums.size()) {result.push_back(path);re…

阿里云数据库服务器价格表查询_一张表精准报价

阿里云数据库服务器价格表&#xff0c;优惠99元一年起&#xff0c;ECS云服务器2核2G、3M固定带宽、40G ESSD Entry云盘&#xff0c;优惠价格99元一年&#xff1b;阿里云数据库MySQL版2核2G基础系列经济版99元1年、2核4GB 227.99元1年&#xff0c;云数据库PostgreSQL、SQL Serve…

9.动态规划——4.最长公共子序列(动态规划类的算法题该如何解决?)

例题——最长公共子序列(一) 分析 设最长公共子序列 d p [ i ] [ j ] dp[i][j] dp[i][j]是 S 1 S_1 S1​的前 i i i个元素&#xff0c;是 S 2 S_2 S2​的前 j j j个元素&#xff0c;那么有&#xff1a; 若 S 1 [ i − 1 ] S 2 [ i − 1 ] S_1[i-1]S_2[i-1] S1​[i−1]S2​[…