c++学习之AVL树

news2025/1/19 3:07:32

目录

一,什么是AVL树

二,AVL树的实现

结构体

insert

左单旋

右单旋

双旋

双旋右边高

双旋左边高

最终实现的插入函数

遍历

判断平衡


一,什么是AVL树

在之前,我们已经了解到了二叉搜索树,提到过它的搜索效率,如果二叉搜索树是一个单支树,那么在查找时,效率此时最低,查找效率和链表一样O(N),而想要提高搜索二叉树的效率,就需要平衡搜索二叉树两端的字数,他们的高度如果能一样,那么查找的效率就是O(logN),效率会提升很多。如何去平衡搜索二叉树呢?俄罗斯的两位数学家G.M.Adelson-Velskii 和E.M.Landis1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这样的树我们称为AVL树。

AVL树具有以下的性质:

.它的左右子树都是AVL树

.左右子树的高度差(平衡因子)的绝对值小于等于1--(-1,0,1)

 

如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n 个结点,其高度可保持在 O(log_2 n) ,搜索时间复杂度 O(log_2 n)。
这里的树的左右子树规定了高度差的绝对值小于等于1,首先对于一个搜索树给定结点的个数的话,就不可能总实现左右两边相等,总有高度差为1的情况,比如两个节点,四个节点等。

二,AVL树的实现

结构体

template<class k, class v>struct AVLTreeNode
{
	AVLTreeNode<k, v>* _left;
	AVLTreeNode<k, v>* _right;
	AVLTreeNode<k, v>* _parent;//增加了一个 parent,用来找平衡因子

	pair<k, v> _kv;//pair表示我们的两个数据(key,value)
    
	int _bf;//平衡因子 balance_factor
		
	//构造函数
	AVLTreeNode(const pair<k, v>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};
template<class k, class v>struct AVLTree
{

	typedef AVLTreeNode< k, v> Node;
.......
  private:
    Node*eoot=nullptr;
};

insert

这里的插入与我们之前的搜索二叉树基本一致,不同的这里我们用的是kv模型,在插入完之后,对于AVL树,我们还需要平衡它,那么如何去平衡它呢?

首先对于一个搜索二叉树,我们在插入一个新节点后,他可能会影响它的祖先的平衡因子(高度差的绝对值).

在这里,我们规定新增节点在平衡因子左边减一,在右边平衡因子加一

这里主要是插入后,处理父亲的平衡因子为1和大于1的时候的情况。

增加节点后,父亲的平衡因子变成一,即高度发生变化,我们需要向上更新祖先的平衡因子,主要操作就是,记录当前节点为parent,重新赋值parent为上一个祖先。

其次对于旋转调整,分为两种情况:

左单旋

左单旋,右子树整体比左子树高,我们需要去调整使得这里祖先的平衡因子小于2。

主要实现:我们将新增节点的parent的parent左节点重新链接到根的右子树上去,在使得我们的parent成为新的根节点,即可实现。

这里,我们可以记录这个节点60为subR,60节点的左孩子为subRL,在传入参数的时候传入父亲节点(parent)。以这样的方式去总结实现左单旋:

//实现左单旋
	void rotateL(Node* parent)
	{
		//找到subR
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//重新链接
		parent->_right = subRL;
		subR->_left = parent;

		//重新给出parent为subR
		parent->_parent = subR;
		Node* ppNode = parent->_parent;
		//subRL的父亲为现在的父亲,需要注意的是这里的sunRL可能为空
		if(subRL)
          subRL->_parent = parent;
		

		//除了以上链接,还有parent的_parent节点重新与新的parent链接
		//判断这里的parent是否为根,如果为根,我们的_parent为空
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			//此时的_parent是存在的
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
		//旋转完成,修改平衡因子,这里变化的也只有parent与subR这两个节点的平衡因子
		parent->_bf = subR->_bf = 0;
	}

右单旋

除了右边比左边高的情况,当然还有左边比右边高的情况,实现右单旋与左单旋的思路一样。

 此时我们记录30这个节点为subL,30的右节点为subLR,此时对应的parent也就是节点60,然后以左单旋相同的思路实现右单旋:


	//实现右单旋
	void rotateR(Node* parent)
	{
		//找到subR
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//重新链接
		parent->_left = subLR;
		if (subLR)
		{
           subLR->_parent = parent;
		}
		  Node* ppNode = parent->_parent;

		subL->_right = parent;
		//重新给出parent为subR
		parent->_parent = subL;
		
		
		//链接parent的_parent节点重新与新的parent链接
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//此时的_parent是存在的
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		//旋转完成,修改平衡因子,这里变化的也只有parent与subR这两个节点的平衡因子
		parent->_bf = subL->_bf = 0;
	}

双旋

除了上述提到的插入情况,新增节点在右树的最右(右边高),左数的最左(左边高),我们分别

通过左旋和右旋的方式降低树的高度,但是当新增节点在右数的左边,左树的右边,那么该如何调整?

双旋右边高

我们再以右边高为情况, 变一个样式,详细的画一下图:

 用两次旋转的方式改变树的高度,

void rotateRL(Node*parent)
	{
		//这里关键的是平衡因子的修改
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//以subRL平衡因子做判断
		int bf = subRL->_bf;//记录
		rotateR(parent->_right);
		rotateL(parent);
		if (bf == 0)
		{
			//subRL自己就是新增节点
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			//新增在subRL的左边,直接修改为旋转后的平衡因子
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			//新增在subRL的右边
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;

		}
		else
		{
			assert(false);
		}
	}

双旋左边高

当新增节点在b处,旋转之后的平衡因子为parent  0,subL 1,subRL -1

当新增节点在c处,旋转之后的平衡因子为parent  -1,subL 0,subRL 1

void rotateLR(Node* parent)
	{
		//这里关键的是平衡因子的修改
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//以subLR平衡因子做判断
		int bf = subLR->_bf;//记录
		rotateR(parent->_parent);
        rotateL(parent->_left);
		if (bf == 0)
		{
			//subLR自己就是新增节点
			parent->_bf = subL->_bf = subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			//新增在subLR的左边,直接修改为旋转后的平衡因子
			parent->_bf =0;
			subL->_bf =1;
			subLR->_bf = 0;
		
		else if (bf == 1)
		{
			//新增在subLR的右边,直接修改为旋转后的平衡因子
			parent->_bf = -1;
			subL->_bf =0;
			subLR->_bf = 0;

		}
		else
		{
			assert(false);
		}
	}

 记录60的平衡因子来确定parent与subRL的平衡因子。

最终实现的插入函数

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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			//这里除了链接cur,还要把我们的parent记录给我们的_parent
			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)
			{
				//父亲的平衡因子不为0,此时高度发生变化,需要向上更新祖先的平衡因子
				cur = parent;
				parent = parent->_parent;
			}
			else  if (parent->_bf == 2 || parent->_bf == -2)
			{
				//平衡因子已经超过1,需要旋转调整处理
				//右边高,左单旋
				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;
	}

遍历

还是中序遍历

void inorder()
	{
		_inorder(_root);
		cout << endl;
	}
	void _inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->_left);
		cout << root->_kv.first<<" ";
		_inorder(root->_right);
	}

判断平衡

直接通过计算左右子树的高度差的绝对值来判断,不过需要再写一个计算子树高度的函数。

bool isbalance()
	{
		return _isbalance(_root);
	}
	int High(Node*root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int lefthigh = High(root->_left);
		int righthigh = High(root->_right);
		return  lefthigh > righthigh ? lefthigh + 1 : righthigh + 1;
	}
	bool _isbalance(Node*root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int treehigh1 = High(root->_left);
		int treehigh2 = High(root->_right);
		if (treehigh2 - treehigh1 != root->_bf)
		{
			cout << "平衡因子异常" << endl;
			return false;
		}
		return abs(treehigh1 - treehigh2)<2&&_isbalance(root->_left) && _isbalance(root->_right);
	}

AVL的最主要部分就是插入的时候,平衡二叉树。

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

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

相关文章

【OS】操作系统课程笔记 第六章 并发性——死锁

6.1 死锁的概念 所谓死锁&#xff0c;是指多个进程因竞争资源而造成的一种僵局&#xff0c;若无外力作用&#xff0c;这些进程都将永远不能再向前推进。 下面举个例子&#xff0c;进程P1已经占用了资源R1&#xff0c;进程P2已经占用了资源R2&#xff0c;而P1和P2都要同时使用…

全自动批量AI改写文章发布软件【软件脚本+技术教程】

项目原理&#xff1a; 利用AI工具将爆款文章改写发布到平台上流量变现,通过播放量赚取收益 软件功能&#xff1a; 1.可以根据你选的文章领域&#xff0c;识别你在网站上抓取的文章链接进来自动洗稿生成过原创的文章&#xff0c;自动配图 2.同时还可以将管理的账号导入进脚本软…

Java基础(第五期): 一维数组 二维数组 数组 引用数据类型在内存中的存储图解

Java基础专栏 文章目录 一、数组介绍和静态初始化1.1 数组初始化1.2 数组的定义格式1.3 数组的静态初始化格式 二、 数组元素访问三、数组遍历操作四、数组遍历求和等练习2.数组求最大值 五、数组动态初始化六、两种初始化的区别七、数组内存图和方法参数传递八、二维数组静态…

深入详解高性能消息队列中间件 RabbitMQ

目录 1、引言 2、什么是 RabbitMQ &#xff1f; 3、RabbitMQ 优势 4、RabbitMQ 整体架构剖析 4.1、发送消息流程 4.2、消费消息流程 5、RabbitMQ 应用 5.1、广播 5.2、RPC VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&am…

【工具使用-信号叠加演示】一种演示不同频率信号叠加的工具

一&#xff0c;简介 本文主要介绍一种网页演示不同频率的正弦信号叠加的工具&#xff0c;供参考。 二&#xff0c;说明 网址&#xff1a;https://teropa.info/harmonics-explorer/ 打开后可以设置不同的信号&#xff0c;然后最上面是不同信号的频率叠加之后的效果&#xff…

Blender vs 3ds Max:谁才是3D软件的未来

在不断发展的3D建模和动画领域&#xff0c;两大软件巨头Blender和3ds Max一直在争夺顶级地位。 随着技术的进步和用户需求的演变&#xff0c;一个重要问题逐渐浮出水面&#xff1a;Blender是否最终会取代3ds Max&#xff1f;本文将深入探讨二者各自的优势和劣势、当前状况&…

2024好用免费的mac苹果电脑杀毒软件CleanMyMac

杀毒软件在苹果家族中是一个小众软件&#xff0c;百度搜索苹果电脑杀毒软件&#xff0c;可能各种杀软良莠不齐&#xff0c;因为在这个市场非常小&#xff0c;绝大多数都是冲着“清理”去的&#xff0c;而不是杀毒。最近测试了一款Mac电脑杀毒软件&#xff0c;杀毒效果也是一般般…

WebDAV之π-Disk派盘 + MiXplorer

MiXplorer是一款非常强大实用的手机文档管理器,能给用户提供了一系列的文档处理功能,包括本地文件浏览、文件排序、文件筛选、切换视图、新建文件、添加收藏等等,同时还能将你手机里的所有文件都罗列出来,简洁明了,让用户一眼就能够找到相应的文件并对其进行编辑,或是删除…

YOLOv5:通过真实结果的txt文件与预测结果的txt文件进行结果评估

YOLOv5&#xff1a;通过真实结果的txt文件与预测结果的txt文件进行结果评估 前言前提条件相关介绍项目结构YOLOv5&#xff1a;通过真实结果的txt文件与预测结果的txt文件进行结果评估val_txt.py输出结果 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批…

Istio快速入门

Istio快速入门 目录 文章目录 Istio快速入门目录本节实战前言1、安装安装方式1.使用 istioctl install2.使用 istioctl manifest generate 安装3.使用 Helm 安装4.使用 Istio Operator 安装 安装 Istio&#x1f6a9; 实战&#xff1a;istioctl 方式安装istio-2023.11.3(测试成功…

SRC实战 | CORS跨资源共享漏洞

CORS跨资源共享 跨源资源共享 (CORS) 是一种浏览器机制&#xff0c;允许网页使用来自其他页面或域的资产和数据。 大多数站点需要使用资源和图像来运行它们的脚本。这些嵌入式资产存在安全风险&#xff0c;因为这些资产可能包含病毒或允许服务器访问黑客。 CORS响应头 CORS通…

物联网AI MicroPython学习之语法 sys系统相关

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; sys 介绍 sys 模块中提供了与micropython运行环境有关的函数和变量。 常量说明 常量定义常量说明sys.argv当前程序启动的可变参数列表sys.byteorder字节顺序 (‘little’ - 小端&#xff0c; ‘big’ - 大…

深入理解强化学习——多臂赌博机:10臂测试平台

分类目录&#xff1a;《深入理解强化学习》总目录 为了大致评估贪心方法和 ϵ − \epsilon- ϵ−贪心方法相对的有效性&#xff0c;我们将它们在一系列测试问题上进行了定量比较。这组问题是2000个随机生成的 k k k臂赌博机问题&#xff0c;且 k 10 k10 k10。在每一个赌博机问…

Python的切片操作详细用法解析

在利用Python解决各种实际问题的过程中&#xff0c;经常会遇到从某个对象中抽取部分值的情况&#xff0c;切片操作正是专门用于完成这一操作的有力武器。理论上而言&#xff0c;只要条件表达式得当&#xff0c;可以通过单次或多次切片操作实现任意切取目标值。切片操作的基本语…

【计算机架构】程序指令计数 | 功耗计算 | 电力功耗 | 安德尔定律(Amdahl‘s Law)

0x00 程序的指令计数 程序的指令计数&#xff08;Instruction Count&#xff09;由程序本身、ISA&#xff08;指令集架构&#xff09;和编译器决定。这表示一个程序中包含的指令数量受到程序编写方式、计算机体系结构和编译器的影响。 每条指令的平均周期数&#xff08;Averag…

如何更改IP地址为美国IP?美国静态住宅代理如何搭建?

相信很多做跨境电商或外贸如TikTok shop、Facebook商店、Amazon、领英的玩家都需要搭建独享的美国IP环境来运营店铺&#xff0c;那么如何搭建稳定独享的IP环境呢&#xff1f;加下来为你详细介绍&#xff0c;助力您的跨境业务。 一、选择合适的代理IP 代理IP可以帮助隐藏用户真…

XSS漏洞利用工具BeEF

BeEF是Browser Exploitation Framework的缩写。随着人们越来越多地关注针对包括移动客户端在内的客户端的网络传播攻击&#xff0c;BeEF使专业的渗透测试人员可以使用客户端攻击向量来评估目标环境的实际安全状况。与其他安全框架不同&#xff0c;BeEF超越了硬化的网络边界和客…

breach1靶机攻略

breach1 准备 这个靶机ip固定为 192.168.110.140 使用vmware的话&#xff0c;将它加入一张仅主机的网卡就行&#xff0c;比如vmnet7&#xff0c;然后vmnet设置成192.168.110.0网段&#xff0c;kali也新建一张网卡加入该网卡 扫描 nmap --min-rate 10000 -p- 192.168.110.1…

登录Tomcat控制台,账号密码输入正确但点击登录没反应不跳转到控制台页面

在tomcat-users.xml里面可以查看登录tomcat控制台的账号密码&#xff0c;如果账号密码输入正确还是登录不进去&#xff0c;则很有可能是tomcat的账号被锁了&#xff08;可在catalina.xxx.log里面查看&#xff09;。tomcat账号被锁定后默认情况是不访问控制台后5分钟自动解锁&am…

第六章:Property-based Testing and Test Oracles

文章目录 Test OraclesActive and Passive Test OraclesTypes of Test OraclesFormal, executable specificationsSolved examplesMetamorphic oraclesAlternative implementations (备用实现)Heuristic oracles (启发式)The Golden Program!Oracle Deviation (Oracle偏差)T…