数据结构·AVL树

news2024/9/23 5:20:33

1. AVL树的概念

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

        一个标准的AVL树任意节点的左右子树高度差的绝对值不大于1,我们将记录高度差的数据称为平衡因子。

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

                                        

2. AVL树节点的定义

                ​​​​​​​        

        我们根STL库保持一致,将key和value合并定义成pair类型,再在节点中添加一个平衡因子。

3. AVL树的插入

        关于平衡因子,我们可以通过其定义得知这样两条条性质:

        1. 插入节点时只会影响到祖先的平衡因子,而不会影响到其他节点的平衡因子。

        2. 父节点右侧插入节点其平衡因子 +1,左侧插入节点其平衡因子 -1

        对于插入节点的父节点来说,插入后父节点的平衡因子只会出现3种情况:第一种情况是平衡因子为0 、第二种情况是平衡因子为1 或 -1 、第三种情况是有祖先平衡因子出现 2 或 -2

        插入后父节点平衡因子为0时

        说明在左侧或右侧插入一个节点后,父节点平衡了,但此时并不会影响除父节点外的所有祖先的平衡因子,因此平衡因子不需要再向上更新了。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        插入后父节点平衡因子为 1 或 -1 时

        说明父节点之前的平衡因子一定为0,左侧插入能使父节点变-1,右侧插入能使父节点变1。为什么父节点平衡因子之前一定为0?因为AVL树的平衡因子只可能出现-1、0、1三种情况。

        此时平衡因子需要向上更新

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        插入后有祖先平衡因子出现 2 或 -2

        说明这个祖先更新前是1或-1,新节点插入在高的那棵树上,进一步加剧了高差,此时已经违反高差不大于1的规则了,此时需要旋转处理

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        平衡因子调整的逻辑就是这样添加到insert函数中的,我会把完整的代码贴到最后的

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4. AVL树的旋转

4.1 左单旋

        插入节点在较高右子树的右侧时就要进行左单旋。

        我这个图中的长方形就代表一颗子树。下面外我们基于这个旋转逻辑编写代码

        经过我们前面更新平衡因子之后找到不符合规则的父节点30,我们进行旋转代码编写的时候不仅仅要弄节点向下的链接内容,还要记得修改节点的parent指针,也就是向上链接的内容。

        我们要更改的主要就是 30(parent节点) 60(subR节点) tree_b(subRL)

                ​​​​​​​        ​​​​​​​        

        但是我们把代码写成这个还是不对的,因为parent的parent没有向下链接。在它向下链接的过程中还有两种情况:就是parent本身就是整颗树的根节点,旋转后subR成为整棵树的根节点;或parent只是树中的一个普通节点,那就要考虑parent的parent向下链接的时候链接左子树还是右子树了。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​​​​​​​​

4.2 右单旋

        当要在较高左子树的左侧插入新节点就要进行右单旋

        右单旋的思路和代码和左单旋是一样的,反过来而已,先向下更新链接内容,再向上更新链接内容,然后更新parentParent的向下链接,最后调整平衡因子

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4.3 右左双旋

        当新节点插入在较高右子树的左侧时就要进行右左双旋。

        如果我们单纯以30为parent或者说旋转基点进行单左旋,结果就是60这棵子树变成30的右子树,但是这棵树还是不平衡的,c比d高出了2,此时再以90为基点右旋结果就是又回去了。

        当较高的树在最两侧的时候我们进行单左旋或单右旋是可以让树平衡的,但是如果较高的树在中间的时候我们进行单左右旋就会让这个较高树一直在中间来回变动,而树一直不会平衡。

        所以AVL就用我图中的方案解决了这一问题。

        ​​​​​​​        

        但是如果我们直接把代码写成这样肯定是不行的,因为平衡因子的问题还没有得到更新。

        如果是我图中画的情况也就是说插入在c树中最后的平衡因子就是 -1、0、0

        如果新节点插入在b树中,最后平衡因子就应该是 0、0、1

        如果 h=0,也就是说60是新插入的节点,最后的平衡因子就应该是 0、0、0

        那具体是这三种情况中的哪种,我们可以通过观察插入后60节点的平衡因子来判断

        ​​​​​​​        ​​​​​​​        ​​​​​​​​​​​​​​

4.4 左右双旋

        当在较高左子树右侧插入新节点时要进行左右双旋

        代码与右左双旋一个思路,注意最后要调整平衡因子。

                        

5. AVL树代码

        删除的思路和插入是一致的,但是删除的更新平衡因子会比较复杂,因为删除在旋转了之后平衡因子可能还要继续向上更新。今天先不写删除了,如果之后有精力我会把删除更新上的。

#include<assert.h>
#include<iostream>
using namespace std;


template<class K, class V>
struct AVLTNode
{

	AVLTNode(const pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}

	pair<K, V> _kv;
	AVLTNode<K, V>* _left;
	AVLTNode<K, V>* _right;
	AVLTNode<K, V>* _parent;
	int _bf;	//balance factor 平衡因子
};



template<class K, class V>
class AVLTree
{
	typedef AVLTNode<K, V> Node;

public:

	//构造
	AVLTree() = default;
	//拷贝构造
	AVLTree(const AVLTree<K, V>& t)
	{
		_root = Copy(t._root);
	}

	//赋值运算符重载
	void operator=(const AVLTree<K, V>& t)
	{
		AVLTree<K, V> new_t(t);
		std::swap(new_t._root, _root);
	}

	//析构
	~AVLTree()
	{
		Destroy(_root);
		_root = nullptr;
	}

	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* Copy(const Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newnode = new Node(root->kv);
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);

		return newnode;
	}



	//插入
	bool Insert(const pair<K, V>& kv);

	//搜索
	Node* Find(const K& x);

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//树的高度
	int Height()
	{
		return _Height(_root);
	}

	//统计节点总个数(插入时可能会有重复数据)
	int Size()
	{
		return _Size(_root);
	}

private:

	//左单旋
	void RotateL(Node* parent);
	//右单旋
	void RotateR(Node* parent);
	//右左双旋
	void RotateRL(Node* parent);
	//左右双旋
	void RotateLR(Node* parent);

	//中序遍历(子函数)
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	
	//树的高度
	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;
	}

	//统计节点总个数(插入时可能会有重复数据)
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

private:
	Node* _root = nullptr;
};


//插入
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* 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 (cur->_kv.first < parent->_kv.first)
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_right = cur;
		cur->_parent = parent;
	}

	//更新平衡因子
	while (parent)
	{
		if (cur == parent->_left)
			parent->_bf--;
		else
			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;
}	


//搜索
template<class K, class V>
AVLTNode<K, V>* AVLTree<K, V>::Find(const K& x)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < x)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > x)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

//左单旋
template<class K, class V>
void AVLTree<K, V>::RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = parent->_right->_left;

	//修改向下链接内容
	parent->_right = subRL;
	subR->_left = parent;
	//修改向上链接内容
	subR->_parent = parent->_parent;
	parent->_parent = subR;
	if (subRL)//防止该树点为空
	{
		subRL->_parent = parent;
	}

	//parent的parent向下链接
	Node* parentParent = subR->_parent;
	if (parentParent == nullptr)//整棵树的根
	{
		_root = subR;
	}
	else
	{
		if (parent == parentParent->_right)
		{
			parentParent->_right = subR;
		}
		else
		{
			parentParent->_left = subR;
		}
	}
	//调整平衡因子
	parent->_bf = 0;
	subR->_bf = 0;
}

//右单旋
template<class K, class V>
void AVLTree<K, V>::RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	//修改向下链接内容
	parent->_left = subLR;
	subL->_right = parent;
	//修改向上链接属性
	subL->_parent = parent->_parent;
	parent->_parent = subL;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//修改parentParent
	Node* parentParent = subL->_parent;
	if (parentParent == nullptr)
	{
		_root = subL;
	}
	else
	{
		if (parent == parentParent->_right)
		{
			parentParent->_right = subL;
		}
		else
		{
			parentParent->_left = subL;

		}
	}
	//更新平衡因子
	subL->_bf = 0;
	parent->_bf = 0;
}

//右左双旋
template<class K, class V>
void AVLTree<K, V>::RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(parent->_right);
	RotateL(parent);
	//更新平衡因子
	if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

//左右双旋
template<class K, class V>
void AVLTree<K, V>::RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RotateL(parent->_left);
	RotateR(parent);
	//更新平衡因子
	if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

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

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

相关文章

音视频入门基础:WAV专题(1)——使用FFmpeg命令生成WAV音频文件

在文章《音视频入门基础&#xff1a;PCM专题&#xff08;1&#xff09;——使用FFmpeg命令生成PCM音频文件并播放》中讲述了生成PCM文件的方法。通过FFmpeg命令可以把该PCM文件转为WAV格式的音频文件&#xff1a; ./ffmpeg -ar 44100 -ac 2 -f s16le -acodec pcm_s16le -i aud…

「Ant Design」Antd 中卡片如何完全不展示内容区域、按需展示内容区域、不展示标题

前言 下面是默认的 Antd 卡片&#xff0c;由以下区域组成 处理 Antd 的 Card 展示形式大致有下面三种 卡片完全不展示内容区域 const App () > (<Card title"Default size card" extra{<a href"#">More</a>} style{{ width: 300 }}b…

工厂数字化转型,该如何建设数字孪生车间?

在工业4.0的浪潮下&#xff0c;数字化转型已成为工厂升级的必然趋势&#xff0c;而数字孪生技术的引入则为这一转型注入了强大动力。智汇云舟作为数字孪生行业头部企业和视频孪生技术首倡者&#xff0c;以创新的视角和前沿的技术&#xff0c;为数字工业建设助力&#xff0c;给众…

《昇思25天学习打卡营第2天|张量》

张量其实就是矩阵&#xff0c;在python中主要是使用numpy这个库来操作&#xff0c;然后再mindspore中一般使用tensor对象作为张量的载体 张量如果维度只有二维的话可以简单理解为数据库中的表&#xff0c;但是如果是3维4维主要是在列表中增加列表项比如 【 【1&#xff0c;1】…

C++ //练习 15.28 定义一个存放Quote对象的vector,将Bulk_quote对象传入其中。计算vector中所有元素总的net_price。

C Primer&#xff08;第5版&#xff09; 练习 15.28 练习 15.28 定义一个存放Quote对象的vector&#xff0c;将Bulk_quote对象传入其中。计算vector中所有元素总的net_price。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块&am…

【C++笔试强训】day01

数字统计 思路 用%10取出个位的数字&#xff0c;用/10迭代。 代码 #include <iostream> using namespace std;int main() {int l, r, res 0;cin >> l >> r;for (int i l; i < r; i){int x i;while (x){if (x % 10 2) res;x / 10;}}cout << r…

AttributeError: ‘WandbLogger‘ object has no attribute ‘bbox_interval‘

一、问题描述 跑yolov5时&#xff0c;运行代码出现以下报错 AttributeError: WandbLogger object has no attribute bbox_interval 二、问题原因 可能是版本的问题&#xff0c;需要把wandb关闭 三、解决方法 在路径 \yolov5\utils\loggers 文件夹下的 _init__.py文件中19…

【MySQL】记录MySQL加载数据(LOAD DATA)

MySQL LOAD DATA 一、背景二、模拟生成用户信息三、加载到mysql表3.1、建表语句3.2 加载数据3.3、查看结果 一、背景 现在有个需求是将用户信息存入student.data文件中&#xff0c;在现在load到数据库中 二、模拟生成用户信息 假设用户信息&#xff0c;包含姓名&#xff0c;…

使用PyTorch导出JIT模型:C++ API与libtorch实战

PyTorch导出JIT模型并用C API libtorch调用 本文将介绍如何将一个 PyTorch 模型导出为 JIT 模型并用 PyTorch 的 CAPI libtorch运行这个模型。 Step1&#xff1a;导出模型 首先我们进行第一步&#xff0c;用 Python API 来导出模型&#xff0c;由于本文的重点是在后面的部署…

AR 眼镜之-蓝牙电话-实现方案

目录 &#x1f4c2; 前言 AR 眼镜系统版本 蓝牙电话 来电铃声 1. &#x1f531; 技术方案 1.1 结构框图 1.2 方案介绍 1.3 实现方案 步骤一&#xff1a;屏蔽原生蓝牙电话相关功能 步骤二&#xff1a;自定义蓝牙电话实现 2. &#x1f4a0; 屏蔽原生蓝牙电话相关功能 …

深入解读 Java 中的 `StringUtils.isNotBlank` 与 `StringUtils.isNotEmpty`

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119@qq.com] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? 专栏导…

【支持语言模型和视觉语言模型的推理引擎sglang】

介绍 sglang是一个AI推理引擎&#xff0c;是一个专门为大语言模型和视觉语言模型设计的高效服务框架。 就像F1赛车需要顶级发动机一样&#xff0c;大语言模型也需要高效的推理引擎来发挥潜力。 而sglang正是这样一个性能怪兽。 根据LMSys组织的官方公告&#xff0c;最新的s…

Docker(十)-Docker运行elasticsearch7.4.2容器实例以及分词器相关的配置

1.下载镜像 1.1存储和检索数据 docker pull elasticsearch:7.4.2 1.2可视化检索数据 docker pull kibana:7.4.22.创建elasticsearch实例 创建本地挂载数据卷配置目录 mkdir -p /software/elasticsearch/config 创建本地挂载数据卷数据目录 mkdir -p /software/elasticse…

【React】JSX:从基础语法到高级用法的深入解析

文章目录 一、什么是 JSX&#xff1f;1. 基础语法2. 嵌入表达式3. 使用属性4. JSX 是表达式 二、JSX 的注意事项1. 必须包含在单个父元素内2. JSX 中的注释3. 避免注入攻击 三、JSX 的高级用法1. 条件渲染2. 列表渲染3. 内联样式4. 函数作为子组件 四、最佳实践 在 React 开发中…

20240724----idea的Java环境卸载与安装

1.删除旧有的jdk https://blog.csdn.net/weixin_42168713/article/details/112162099 &#xff08;补充&#xff1a;我把用户变量和java有关的都删了&#xff09; 2.下载新的jdk百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1gkuLoxBuRAtIB1IzUTmfyQ 提取码&#xf…

第二代欧洲结构设计标准简介

文章目录 0、背景1、总览2、更新及变化2.1 抗震2.2 地基基础2.3 防火 0、背景 本篇文章来自微信公众号土木吧&#xff0c;原作者李立昌&#xff08;北京鑫美格工程设计有限公司&#xff09;。对原文感兴趣的可以点击这里。 新的欧标滚滚而来&#xff0c;提前做好准备很有必要…

人工智能视频大模型:重塑视频处理与理解的未来

目录 一、人工智能视频大模型概述 1.1 定义与特点 1.2 技术基础 二、关键技术解析 2.1 视频特征提取 2.2 时空建模 2.3 多任务学习 三、应用场景展望 3.1 视频内容分析 3.2 视频编辑与生成 3.3 交互式视频体验 四、未来发展趋势 4.1 模型轻量化与移动端部署 4.2 …

前端面试项目细节重难点分享(十三)

面试题提问&#xff1a;分享你最近做的这个项目&#xff0c;并讲讲该项目的重难点&#xff1f; 答&#xff1a;最近这个项目是一个二次迭代开发项目&#xff0c;迭代周期一年&#xff0c;在做这些任务需求时&#xff0c;确实有很多值得分享的印象深刻的点&#xff0c;我讲讲下面…

【C语言】队列的实现(数据结构)

前言&#xff1a; 相信大家在生活中经常排队买东西&#xff0c;今天学习的队列就跟排队买东西一样&#xff0c;先来买的人就买完先走&#xff0c;也就是先进先出。废话不多说&#xff0c;进入咱们今天的学习吧。 目录 前言&#xff1a; 队列的概念 队列的实现 队列的定义 …

【8月EI会议推荐】第四届区块链技术与信息安全国际会议

一、会议信息 大会官网&#xff1a;http://www.bctis.nhttp://www.icbdsme.org/ 官方邮箱&#xff1a;icbctis126.com 组委会联系人&#xff1a;杨老师 19911536763 支持单位&#xff1a;中原工学院、西安工程大学、齐鲁工业大学&#xff08;山东省科学院&#xff09;、澳门…