C++进阶篇4---番外-AVL树

news2024/12/23 22:08:21

一、AVL树的概念

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

【注意】平衡因子是用右子树的高度减去左子树的高度得到的

 

二、AVL树结点的定义

template<class K,class V>
struct AVLTreeNode {
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	pair<K, V> _kv;
	int _bf;


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

 三、AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
AVL树的插入过程可以分为两步:
  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
template<class K,class V>
class AVLTree {
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if(cur->_kv.first>kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first )
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		//看树是否还保持平衡
		while (parent)
		{
			//先调整平衡因子---因为插入的结点是叶子节点,所以父结点的平衡因子必然发生变化
            //在根据平衡因子的计算公式height_r - height_l,判断平衡因子的变化
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			//看是否需要调整以及如何调整
			//...
		}
	}
private:
	Node* _root = nullptr;
};

上面代码的插入逻辑和二叉搜索树很相似,这里不多讲了(忘记的或者不了解的可以去看二叉搜索树),主要看如何判断树是否平衡以及如何调整使得树保持平衡

这里主要分三种情况:

1、父节点的平衡因子变成0,则树保持平衡,不需要变化

解释:父节点的平衡因子变成0,说明之前未正负1,只有如下两种情况

2、父节点的平衡因子变成正负1,则该子树的高度发生变化,但该子树依旧平衡,要看它的父节点所在的子树是否还能保持平衡

 

3、父结点的平衡因子变成正负2,则该子树的不能保持平衡,需要进行旋转调整

template<class K,class V>
class AVLTree {
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if(cur->_kv.first>kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first )
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		//看树是否还保持平衡
		while (parent)
		{
			//先调整平衡因子
			if (parent->_left == cur)
			{
				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)
			{
				//分4种情况:左单旋,右单旋,先左旋在右旋,先右旋在左旋
				//...

                //旋转完成后子树就平衡了=> 整个树都平衡了,直接退出循环
                break;
			}
			else
			{
				//如果进入这里,说明前面的代码出错
				assert(0);
			}
		}
	}
private:
	Node* _root = nullptr;
};

 四、旋转调整

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

代码如下

	void _RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (subRL)//注意h==0的情况
			subRL->_parent = parent;
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
		}
		subR->_bf = parent->_bf = 0;
	}

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

注意事项同上。

代码如下 

void _RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;
		if (subLR)//注意h==0的情况
			subLR->_parent = parent;
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
		}
		subL->_bf = parent->_bf = 0;
	}

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

代码如下

	void _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 = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = subRL->_bf = 0;
		}
		else
		{
			subR->_bf = 1;
			parent->_bf = subRL->_bf = 0;
		}
	}

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

这个留给读者思考 

附:

//完整版代码
template<class K,class V>
struct AVLTreeNode {
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	pair<K, V> _kv;
	int _bf;


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

template<class K,class V>
class AVLTree {
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<K,V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		while (parent)
		{
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)//为0,说明之前_bf=-1/1,即子树的高度没有发生变化
			{
				break;
			}	
			else if (parent->_bf == 1 || parent->_bf == -1)//为正负1,说明之前_bf=0,即子树的高度发生变化,并且会影响到上层祖宗结点
			{
				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 if (parent->_bf == 2 && cur->_bf == -1)//右旋左旋
				{
					_RotateRL(parent);
				}
				
				break;//旋转之后整个树就平衡了,直接跳出循环
			}
			else
			{
				//这种情况不可能发生,如果发生就说明程序出错
				assert(false);
			}
		}
		return true;
	}

	void _RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		_RotateL(parent->_left);
		_RotateR(parent);
		if (bf == 1)
		{
			subL->_bf = -1;
			subLR->_bf = parent->_bf = 0;
		}
		else if(bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = subLR->_bf = 0; 
		}
		else//bf==0,插入的结点就是subLR
		{
			parent->_bf = subL->_bf = subLR->_bf = 0;
		}
	}

	void _RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		_RotateR(parent->_right);
		_RotateL(parent);
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = subRL->_bf = 0;
		}
		else if(bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = subRL->_bf = 0;
		}
		else //bf==0,插入的结点就是subLR
		{
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
	}

	//右单旋
	void _RotateR(Node*parent)
	{
		Node* subL = parent->_left;//找到要作为新根的结点
		Node* pParent = parent->_parent;//找到该子树的父亲结点
		Node* subLR = subL->_right;
		subL->_right = parent;
		parent->_parent = subL;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		if (_root == parent)//如果是根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
		}
		parent->_bf = subL->_bf = 0;
	}

	//左单旋
	void _RotateL(Node* parent)
	{
		Node* subR = parent->_right;//找到要作为新根的结点
		Node* pParent = parent->_parent;//找到该子树的父亲结点
		Node* subRL = subR->_left;//找到要被"过继"的孩子结点
		subR->_left = parent;
		if (subRL)//如有"过继"结点
			subRL->_parent = parent;
		parent->_parent = subR;
		parent->_right = subRL;
		if (_root == parent)//如果是根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
		}
		parent->_bf = subR->_bf = 0;
	}


	bool Isbalance()
	{
		return _Isbalance(_root);
		//return _Isbalance(_root) >= 0;
	}
	bool _Isbalance(Node*root)
	{
		if (root == nullptr)
			return true;
		int left = _Height(root->_left);
		int right = _Height(root->_right);
		if (abs(right - left) > 1)
		{
			cout << root->_kv.first << ":" << root->_kv.second << endl;
			return false;
		}
		if (right - left != root->_bf)
		{
			cout << root->_kv.first << ":"<< "平衡因子出错" << endl;
			return false;
		}
		return _Isbalance(root->_left) && _Isbalance(root->_right);
	}

	size_t size()
	{
		return _size(_root);
	}

	size_t Height()
	{
		return _Height(_root);
	}

private:
	size_t _Height(Node*root)
	{
		if (root == nullptr)
			return 0;
		return max(_Height(root->_left),_Height(root->_right)) + 1;
	}
	size_t _size(Node* root)
	{
		if (root == nullptr)
			return 0;
		return 1 + _size(root->_left) + _size(root->_right);
	}

    //如果单纯判断是否平衡可以这么写,-1表示不平衡,>=0表示平衡
	//int _Isbalance(Node* root)
	//{
	//	if (root == nullptr)
	//		return 0;
	//	int left = _Isbalance(root->_left);
	//	if (left < 0) return -1;
	//	int right = _Isbalance(root->_right);
	//	if (right < 0) return -1;
	//	if (abs(right - left) > 1 || right - left != root->_bf)
	//	{
	//		return -1;
	//	}
	//	return max(left, right) + 1;
	//}
private:
	Node* _root = nullptr;
};

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

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

相关文章

Microsoft Dynamics 365 CE 扩展定制 - 8. DevOps

在本章中,我们将介绍以下内容: 使用PowerShell导出Dynamics 365解决方案使用PowerShell部署解决方案构建解决方案层次结构修补解决方案暂存解决方案使用SolutionPackager在源代码管理中保存解决方案使用PackageDeployer将您的解决方案与配置数据打包基于解决方案版本增量触发…

缓存-基础理论和Guava Cache介绍

缓存-基础理论和Guava Cache介绍 缓存基础理论 缓存的容量和扩容 缓存初始容量、最大容量&#xff0c;扩容阈值以及相应的扩容实现。 缓存分类 本地缓存&#xff1a;运行于本进程中的缓存&#xff0c; 如Java的 concurrentHashMap, Ehcache&#xff0c;Guava Cache。 分布式缓…

3.Netty中Channel通道概述

Selector 模型 Java NIO 是基于 Selector 模型来实现非阻塞的 I/O。Netty 底层是基于 Java NIO 实现的&#xff0c;因此也使用了 Selector 模型。 Selector 模型解决了传统的阻塞 I/O 编程一个客户端一个线程的问题。Selector 提供了一种机制&#xff0c;用于监视一个或多个 …

如何成为C++大神?五个技巧助你提升编程水平

一名优秀的C程序员是如何炼成的&#xff1f;这个问题一直困扰着许多人&#xff0c;尤其是那些刚刚踏入编程的世界的新手。C作为一门强大而复杂的编程语言&#xff0c;的确需要一些特殊的技巧和策略才能掌握。但幸运的是&#xff0c;成为一名出色的C程序员并不是不可能的任务。在…

【算法练习Day41】买卖股票的最佳时机买卖股票的最佳时机 II

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 买卖股票的最佳时机买卖股票…

办公神器!2024年值得拥有的10款在线画板软件!

随着科技的进步和互联网的普及&#xff0c;我们工作、学习和生活方式发生了翻天覆地的变化。在线画板软件就是在这个背景下应运而生的一种便捷工具。它不仅满足了我们随时随地绘制图像、演示思路的需求&#xff0c;还提供了协同编辑、云存储等功能&#xff0c;使得团队协作变得…

Java面试题(高频、有答案,全网最强)

原文网址&#xff1a;Java面试题&#xff08;高频、有答案&#xff0c;全网最强&#xff09;-CSDN博客 这是一套全网最强的Java面试题&#xff0c;吊打网上所有Java面试题。 此套面试题的威力&#xff1a;看过这套题的朋友、同事、粉丝参加了面试后说&#xff0c;他们面试被问…

基于C#的GRPC

GRPC gRPC&#xff08;gRPC Remote Procedure Call&#xff09;是由Google开发的高性能、跨语言的远程过程调用框架。它基于HTTP/2协议进行通信&#xff0c;支持多种编程语言&#xff0c;包括C, C#, Java, Python等&#xff0c;使不同语言的应用程序可以通过远程调用相互通信。…

SPASS教程-入门

常用的统计工具 EXCEL 严格说来并不是统计软件&#xff0c;但作为数据表格软件&#xff0c;有一定统计计算功能。对于简单分析&#xff0c;Excel还算方便&#xff0c;但随着问题的深入&#xff0c;Excel就不那么“傻瓜”&#xff0c;需要使用函数&#xff0c;甚至根本没有相应…

​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​

软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】 课本里章节里所有蓝色字体的思维导图

Spire.Office for Java 8.10.2 同步更新Crk

Spire.Office for Java 是 E-iceblue 提供的企业级 Office Java API 的组合。它包括Spire.Doc for Java、Spire.XLS for Java、Spire.Presentation for Java、Spire.PDF for Java和Spire.Barcode for Java。 开发人员可以使用Spire.Office for Java在Java应用程序中执行各种办…

【electron】【附排查清单】记录一次逆向过程中,fetch无法请求http的疑难杂症(net::ERR_BLOCKED_BY_CLIENT)

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ Adblock等插件拦截2️⃣ 【失败】Content-Security-Policy启动服务器json-serverhtml中的meta字段 3️⃣ 【失败】https vs httpwebPreferences & allowRunningInsecureContent disable-features 4️⃣ 【失败】检测fetch…

技术分享 | app自动化测试(Android)--元素定位方式与隐式等待

元素定位是 UI 自动化测试中最关键的一步&#xff0c;假如没有定位到元素&#xff0c;也就无法完成对页面的操作。那么在页面中如何定位到想要的元素&#xff0c;本小节讨论 Appium 元素定位方式。 Appium的元素定位方式 定位页面的元素有很多方式&#xff0c;比如可以通过 I…

初识Java 17-2 反射

目录 转型前检查 构建例子&#xff1a;生成层次结构 优化Creator&#xff1a;使用类字面量 优化PetCounter&#xff1a;动态验证类型 更通用的递归计数 注册工厂 本笔记参考自&#xff1a; 《On Java 中文版》 转型前检查 当我们使用传统的类型转换&#xff0c;例如&…

实战!工作中常用的设计模式

文章目录 前言一、策略模式1.1、 业务场景1.2 、策略模式定义1.3、 策略模式使用1.3.1、一个接口&#xff0c;两个方法1.3.2、不同策略的差异化实现1.3.3、使用策略模式 二、责任链模式2.1、业务场景2.2、责任链模式定义2.3、责任链模式使用2.3.1、一个接口或者抽象类2.3.2、每…

11.7加减计数器,可置位~,数字钟分秒,串转并,串累加转并,24位串并128,流水乘法器,一些乘法器

信号发生器 方波&#xff0c;就是一段时间内都输出相同的信号 锯齿波就是递增 三角波就是先增后减 加减计数器 当mode为1则加&#xff0c;Mode为0则减&#xff1b;只要为0就输出zero 这样会出问题&#xff0c;因为要求是十进制&#xff0c;但是这里并没有考虑到9之后怎么办&a…

openvino学习(一)ubuntu20.04安装openvino2022

安装openvino2022要求 操作系统 Ubuntu 18.04 长期支持 (LTS)&#xff0c;64 位 Ubuntu 20.04 长期支持 (LTS)&#xff0c;64 位 软件 CMake 3.13 或更高版本&#xff0c;64 位 GCC 7.5.0&#xff08;适用于 Ubuntu 18.04&#xff09;或 GCC 9.3.0&#xff08;适用于 Ubunt…

工具介绍——第三方软件远程连接(工具:Rustdesk)

文章目录 前言一、使用工具二、开始演示1、拿下目标主机权限后上传文件2、运行目标主机上的rustdesk-1.1.9.exe文件3、目标主机上whoami查看现在的用户4、查找目标主机上连接的文件&#xff0c;并添加连接密码5、目标主机重启rustdesk的应用程序6、本地连接主机 前言 这里主要…

“第六十三天”

这两天怎么做的这么别扭&#xff0c;为什么我的vs 的strlen函数包括终止字符了&#xff1b; 哦哦&#xff0c;明白了&#xff0c;fgets函数读取在未达到指定字长&#xff0c;或者遇见空白符之前&#xff0c;会读取前面的所有字符&#xff0c;所以会读取换行符&#xff0c;而get…

康耐视深度学习ViDi-View菜单介绍

Accept View承认当前图片标注的有效性 Clear Marking 清除当前图片的标注特征 Clear Marking & Labels清除当前图片的标注特征和标签 Process处理当前图片 Edit ROI编辑检测的区域 Edit Regions编辑(标注)特征区域 Edit Mask 编辑遮挡(屏蔽)区域 Apply Mask To Tool将遮挡(…