数据结构——AVL树

搜索二叉树能够在二叉树情况比较好的情况下,使查找的时间复杂度达到O(logN)。
但是,它的查找的时间复杂度依旧是O(N),面临的情况是所有的树都只有左/右子树的情况下。
那么今天介绍的AVL树就是解决这一情况的。
但是由于AVL树对我来说有些复杂,所以只讨论插入节点。这其实也有了查和改,没有删除。

文章目录

  • 1.AVL树
    • 1). 初步认识AVL树
    • 2). AVL树的节点
    • 3). AVL树的节点的插入
    • 4). AVL树的旋转
      • a. 左单旋
      • b. 情况举例
      • c. 右单旋
      • d. 右左双旋
      • e. 左右双旋
    • 5). 检验与总结

1.AVL树

1). 初步认识AVL树

AVL树是在搜索树的基础上对搜索树提出了额外的规则和调整的方法的一种树,它提出了 平衡因子的概念,利用平衡因子来体现搜索树是否平衡,而当平衡因子是非常规值的时候就会通过旋转树来让平衡因子重新达到平衡。
而平衡因子就是一棵树的右子树的高度减去左子树的高度,这个值的绝对值不大于 1,则认为这棵树是平衡的举个例子:
在这里插入图片描述

像这棵树就是平衡二叉树,
在这里插入图片描述

而这棵树不符合上述我们对平衡二叉树的要求,所以不是平衡二叉树,得需要旋转调整:
在这里插入图片描述
现在,它就是一颗平衡二叉树了。所以接下来来让我们实现这一数据结构:

2). AVL树的节点

与二叉搜索树不同,平衡二叉树中多了两个成员,一个是平衡因子,一个是指向父亲节点的指针:
而这里我少做了些调整,将存储数据换为pair结构体的Key-Value形。

template<class Key, class Val>
struct AVLTreeNode
{
	AVLTreeNode<Key, Val>* _left;
	AVLTreeNode<Key, Val>* _right;
	AVLTreeNode<Key, Val>* _parent;
	pair<Key, Val> _kv;

	int _bf;//balance factor

	AVLTreeNode(const pair<Key, Val>& pa)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(pa)
		,_bf(0)
	{}

};

3). AVL树的节点的插入

AVL树前面说过,它是在二叉搜索树的基础上增加一些规则形成的,所以前半部分与二叉搜索树的插入一样:

bool Insert(const pair<Key, Val>& pa)
{
	if (_root == nullptr)
	{
		_root = new Node(pa);
		size++;
		return true;
	}
	else
	{
		//正常按搜索树规则插入节点
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			parent = cur;
			if (cur->_kv.first > pa.first)
				cur = cur->_left;
			else if (cur->_kv.first < pa.first)
				cur = cur->_right;
			else
				return false;
		}

		cur = new Node(pa);

		if (parent->_kv.first < cur->_kv.first) 
			parent->_right = cur;
		else if(parent->_kv.first > cur->_kv.first) 
			parent->_left = cur;

		//更新插入节点的父亲节点
		cur->_parent = parent;


		//开始遵守平衡二叉树的规则。。。
		
		return true;
	}
}

当插入节点后,再开始更新平衡因子,然后根据平衡因子进行调整。我们可以从中找出一些规律后开始书写代码:以这棵树为例:
在这里插入图片描述

我们先插入一个数字为33的节点会得到这样的情况:

在这里插入图片描述
我们插入一个数字为25的节点会得到这样的情况:

在这里插入图片描述
我们插入一个数字为55的节点会得到这样的情况:
在这里插入图片描述
我们发现我们插入的新节点的平衡因子总是0,而且新增节点只会影响祖先节点,不会影响旁支
我们还发现,当向上更新祖先平衡因子时并不是只更新父亲,也不是一直更新到根节点,那这有没有规律呢?其实是有的:

当父亲节点的平衡因子更新后变成0的时候,说明它是从-1或者1变来的,也说明了它增加这个节点之后
并没有增加父亲这颗子树的高度,所以更新了父亲节点的平衡因子就不需要向上更新了。

而当父亲节点的平衡因子更新后变成-1/1的时候,说明原来父亲节点的平衡因子是0,处于一种平衡的状态,
而后又打破了平衡,增加了父亲子树的高度,所以需要更新父亲节点的平衡因子后继续向上更新。

而且新增节点是父亲节点的左子树的时候父亲的平衡因子++,右子树时--

依据上述我们可以写出部分代码:

			while (parent)
			{
				//调整平衡因子
				//如果插入节点在父亲节点的左边,减减
				//右边加加
				if (parent->_left == cur) parent->_bf--;
				else if(parent->_right == cur) parent->_bf++;

				//如果调整平衡因子后父亲节点的平衡因子为0,说明以前的高度是1或者-1,高度没发生变化不用向上调整
				//如果为1或者-1需要向上调整平衡因子
				if (parent->_bf == 0) break;
				else if (parent->_bf == -1 || parent->_bf == 1) parent = parent->_parent, cur = cur->_parent;
				else 
				{
					
				}
			}

这是我们碰到插入节点后平衡因子没有出现违反规则的情况,那假如我们插入一个65的节点呢?
在这里插入图片描述
我们发现它的平衡因子就不符合规则了,那这种情况下,就需要旋转来调整。

4). AVL树的旋转

由于二叉树所种多样,不可能将所有的情况都一一列举,所以我们使用一种统一的视角来看待AVL树的旋转:
我们在上面碰到的两种不平衡的情况都是需要左单旋来解决:

a. 左单旋

在这里插入图片描述

最后一个树中,a是一颗高度为h的子树,b、c是高度为h-1的子树,然后在c子树中增加新节点。导致根节点(暂且是根节点)的平衡因子变成了2。需要旋转:
在这里我们对一些节点起一些名字:
在这里插入图片描述

这时候需要我们对parent进行一次左旋,旋转的具体做法就是将subR的左子树subLR给parent,
变成parent的右子树,然后把parent变为subR的左子树:
在这里插入图片描述
此时我们可以看到,parent的平衡因子变成了0,subR的变成了0。
接下来代码实现:

void RotateL(Node* parent)
{
	Node* subRL = parent->_right->_left;
	Node* subR = parent->_right;
	Node* graparent = parent->_parent;

	parent->_right = subRL;
	parent->_parent = subR;


	if(subRL)//这里处理c就是新增的情况,防止subL为空
		subRL->_parent = parent;

	subR->_left = parent;
	subR->_parent = graparent;

	if (graparent)
	{
		if (graparent->_left == parent) graparent->_left = subR;
		else graparent->_right = subR;
	}
	else
		_root = subR;

	parent->_bf = subR->_bf = 0;
}
而左单旋的特征就是:parent的平衡因子是2,subR为1

b. 情况举例

为什么不一一列举,假如上面的h-1等于2的话,说明abc都是一个高度为2的子树那么首先高度为2的子树就有3种情况,而在c树上新增一个节点可能有8种情况,组合一下就是216种情况,列举不完…
在这里插入图片描述

c. 右单旋

右单旋和左单旋对称,不多说,代码如下:

void RotateR(Node* parent)
{
	Node* subLR = parent->_left->_right;
	Node* subL = parent->_left;
	Node* graparent = parent->_parent;

	parent->_left = subLR;
	parent->_parent = subL;


	if (subLR)
		subLR->_parent = parent;

	subL->_right = parent;
	subL->_parent = graparent;

	if (graparent)
	{
		if (graparent->_left == parent) graparent->_left = subL;
		else graparent->_right = subL;
	}
	else
		_root = subL;

	parent->_bf = subL->_bf = 0;
}
而右单旋的特征就是:parent的平衡因子是-2,subR为-1

d. 右左双旋

我们插入节点的时候可能会有这种情况,例如我们插入55:
在这里插入图片描述
这次我们再用一次左单旋:
在这里插入图片描述
没有什么变化,所以我们得使用双旋:
这时候我们需要再使用统一的图来说明:
在这里插入图片描述
再起名字:
在这里插入图片描述
这时候双旋需要我们将subR右旋:
在这里插入图片描述

然后再左旋parent:
在这里插入图片描述

如果有细心的人发现,这其实就是把b子树做了parent的右子树,c子树做了subR,然后subRL做了parent和subR的父亲,这其中新增节点的位置影响着parent和subR的平衡因子,调整方案如下:

当新增节点在b上,也就是subRL的平衡因子为-1时,parent的平衡因子为0,subRL的为0, subR的为1
当新增节点在c上,也就是subRL的平衡因子为1时,parent的平衡因子为-1,subRL的为0, subR的为0
当然还有subRL本身就是新增的情况,subRL的平衡因子为0,此时parent、subRsubRL的平衡因子都是0

代码如下:

void RotateRL(Node* parent)
{
	int subRL_bf = parent->_right->_left->_bf;
	Node* subR = parent->_right;
	Node* subRL = parent->_right->_left;

	RotateR(parent->_right);
	RotateL(parent);

	if (subRL_bf == 0)
		parent->_bf = subR->_bf = subRL->_bf = 0;
	else if (subRL_bf == 1)
	{
		subRL->_bf = subR->_bf = 0;
		parent->_bf = -1;
	}
	else if (subRL_bf == -1)
	{
		parent->_bf = subRL->_bf = 0;
		subR->_bf = 1;
	}
}

e. 左右双旋

在这里插入图片描述
与右左双旋对称不多赘述,代码如下:

void RotateLR(Node* parent)
{
	int subLR_bf = parent->_left->_right->_bf;
	Node* subL = parent->_left;
	Node* subLR = parent->_left->_right;

	RotateL(parent->_left);
	RotateR(parent);

	if (subLR_bf == 0)
		parent->_bf = subL->_bf = subLR->_bf = 0;
	else if (subLR_bf == 1)
	{
		subLR->_bf = parent->_bf = 0;
		subL->_bf = -1;
	}
	else if (subLR_bf == -1)
	{
		subL->_bf = subLR->_bf = 0;
		parent->_bf = 1;
	}
}

5). 检验与总结

上面就是AVL树的最重要的点,我们在创建好一颗平衡二叉树的时候也应该检查,它是否真的平衡。而平衡二叉树的的规则就是高度的限制,所以我们只需要看每棵树的左右子树的高度差即可,代码如下:

	bool IsBlance()
	{
		return _IsBlance(_root);
	}
	
	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = Height(root->_left);
		int right = Height(root->_right);

		return left > right ? 1 + Height(root->_left) : 1 + Height(root->_right);
	}

	int _IsBlance(Node* root)
	{
		if (root == nullptr)
			return true;
		int left = Height(root->_left);
		int right = Height(root->_right);

		return abs(right - left) > 1 ? false : _IsBlance(root->_left)
			&& _IsBlance(root->_right);
	}

最后附上整合好的AVL树:

template<class Key, class Val>
struct AVLTreeNode
{
	AVLTreeNode<Key, Val>* _left;
	AVLTreeNode<Key, Val>* _right;
	AVLTreeNode<Key, Val>* _parent;
	pair<Key, Val> _kv;

	int _bf;

	AVLTreeNode(const pair<Key, Val>& pa)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(pa)
		,_bf(0)
	{}

};


template<class Key, class Val>
class AVLTRree
{
	typedef AVLTreeNode<Key, Val> Node;
public:
	int size = 0;

	bool Insert(const pair<Key, Val>& pa)
	{
		if (_root == nullptr)
		{
			_root = new Node(pa);
			size++;
			return true;
		}
		else
		{
			Node* cur = _root;
			Node* parent = _root;
			while (cur)
			{
				parent = cur;
				if (cur->_kv.first > pa.first)
					cur = cur->_left;
				else if (cur->_kv.first < pa.first)
					cur = cur->_right;
				else
					return false;
			}

			cur = new Node(pa);

			if (parent->_kv.first < cur->_kv.first) 
				parent->_right = cur;
			else if(parent->_kv.first > cur->_kv.first) 
				parent->_left = cur;

			cur->_parent = parent;

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

				if (parent->_bf == 0) break;
				else if (parent->_bf == -1 || parent->_bf == 1) parent = parent->_parent, cur = cur->_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);
					}
					else assert(false);
				}
				else assert(false);
			}
			size++;
			return true;
		}
	}

	bool IsBlance()
	{
		return _IsBlance(_root);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

private:

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		//cout << root->_kv.first << " ";
		printf("%2d ", root->_kv.first);
		_InOrder(root->_right);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = Height(root->_left);
		int right = Height(root->_right);

		return left > right ? 1 + Height(root->_left) : 1 + Height(root->_right);
	}

	int _IsBlance(Node* root)
	{
		if (root == nullptr)
			return true;
		int left = Height(root->_left);
		int right = Height(root->_right);

		return abs(right - left) > 1 ? false : _IsBlance(root->_left)
			&& _IsBlance(root->_right);
	}

	void RotateLR(Node* parent)
	{
		int subLR_bf = parent->_left->_right->_bf;
		Node* subL = parent->_left;
		Node* subLR = parent->_left->_right;

		RotateL(parent->_left);
		RotateR(parent);

		if (subLR_bf == 0)
			parent->_bf = subL->_bf = subLR->_bf = 0;
		else if (subLR_bf == 1)
		{
			subLR->_bf = parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (subLR_bf == -1)
		{
			subL->_bf = subLR->_bf = 0;
			parent->_bf = 1;
		}
	}

	void RotateRL(Node* parent)
	{
		int subRL_bf = parent->_right->_left->_bf;
		Node* subR = parent->_right;
		Node* subRL = parent->_right->_left;

		RotateR(parent->_right);
		RotateL(parent);

		if (subRL_bf == 0)
			parent->_bf = subR->_bf = subRL->_bf = 0;
		else if (subRL_bf == 1)
		{
			subRL->_bf = subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (subRL_bf == -1)
		{
			parent->_bf = subRL->_bf = 0;
			subR->_bf = 1;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subRL = parent->_right->_left;
		Node* subR = parent->_right;
		Node* graparent = parent->_parent;

		parent->_right = subRL;
		parent->_parent = subR;


		if(subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		subR->_parent = graparent;

		if (graparent)
		{
			if (graparent->_left == parent) graparent->_left = subR;
			else graparent->_right = subR;
		}
		else
			_root = subR;

		parent->_bf = subR->_bf = 0;
	}

	void RotateR(Node* parent)
	{
		Node* subLR = parent->_left->_right;
		Node* subL = parent->_left;
		Node* graparent = parent->_parent;

		parent->_left = subLR;
		parent->_parent = subL;


		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;
		subL->_parent = graparent;

		if (graparent)
		{
			if (graparent->_left == parent) graparent->_left = subL;
			else graparent->_right = subL;
		}
		else
			_root = subL;

		parent->_bf = subL->_bf = 0;
	}

	Node* _root = nullptr;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ElementUI表格el-table自适应高度(表头表尾固定不动)

ElementUI表格el-table自适应高度&#xff08;表头表尾固定不动&#xff09;&#xff0c;内容只在中间滚动&#xff0c;效果如图&#xff1a; 实现代码 <div class"mt-10" :style"{height:tableHeight}"><div class"operation-bar">…

算法通关村第八关-黄金挑战

大家好我是苏麟 ...... 路径总和2 描述 : 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 题目 : LeetCode 113.路径总和2 113. 路径总和 II 分析 : 这…

uni-app的下拉搜索选择组合框

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家介绍uni-app中一款可以搜索下拉选择输入框的插件 下拉搜索选择组合框 superwei-combox 组合框 uni-app中可下拉搜索选…

智能配方颗粒管理系统解决方案,专业实现中医药产业数字化-亿发

“中药配方颗粒”&#xff0c;又被称为免煎中药&#xff0c;源自传统中药饮片&#xff0c;经过提取、分离、浓缩、干燥、制粒、包装等工艺加工而成。这种新型配方药物完整保留了原中药饮片的所有特性。既能满足医师的辨证论治和随症加减需求&#xff0c;同时具备强劲好人高效的…

模拟退火算法MATLAB实现

介绍 算法试图随着控制参数T的降低&#xff0c;使目标函 数值f&#xff08;内能E&#xff09;也逐渐降低&#xff0c;直至趋于全局最 小值&#xff08;退火中低温时的最低能量状态&#xff09;&#xff0c;算法 工作过程就像固体退火过程一样。 Metropolis准则——–以概率接受…

有什么价格实惠的猫罐头?2023良心性价比的猫罐头推荐!

选购猫罐头至关重要&#xff0c;好的猫罐头不仅营养丰富&#xff0c;水分充足&#xff0c;适口性佳&#xff0c;还能易于消化吸收。然而&#xff0c;若选择不当&#xff0c;可能不仅无法达到预期效果&#xff0c;甚至可能产生负面影响。 作为一个从事宠物行业7年的宠物店店长&…

upload-labs关卡4(黑名单点空格绕过或htaccess绕过)通关思路

文章目录 前言一、回顾上一关知识点二、靶场第四关方法一通关思路1.看源码2、点空格绕过 三、靶场第四关方法二通关思路1、htaccess文件是什么2、通过上传htaccess文件进行绕过1、使用前提2、上传htaccess文件&#xff0c;然后再上传phpinfo的jpg文件 总结 前言 此文章只用于学…

阶段七-Day04-Spring03

一、Sping声明式事务 1. 编程式事务介绍 整个事务控制的代码都需要程序员自己编写。包含&#xff1a;开启事务&#xff08;openSession()&#xff0c;创建SqlSession时MyBatis底层自动创建Transaction对象&#xff09;、提交事务(session.commit())、回滚事务(session.rollba…

Shotcut for Mac/Win:免费的开源视频编辑软件

Shotcut 是一款免费的开源视频编辑软件&#xff0c;允许用户为各种目的编辑和创建视频。它适用于 Windows、Mac 和 Linux 操作系统。Shotcut 具有用户友好的界面&#xff0c;并提供一系列功能&#xff0c;例如支持多种视频格式、音频过滤器和视频效果。 Shotcut的一些主要功能…

C++拷贝构造函数和运算符重载

目录 一&#xff0c;拷贝构造函数 二&#xff0c;运算符重载 一&#xff0c;拷贝构造函数 概念&#xff1a;在类的定义中&#xff0c;构造函数只是单纯将内置类型进行初始化&#xff0c;而拷贝构造函数是将整个类进行拷贝到另一个类中进行初始化。在定义拷贝构造函数时&…

bclinux aarch64 ceph 14.2.10 文件存储 Ceph File System, 需要部署mds: ceph-deploy mds

创建池 [rootceph-0 ~]# ceph osd pool create cephfs_data 64 pool cephfs_data created [rootceph-0 ~]# ceph osd pool create cephfs_metadata 32 pool cephfs_metadata created cephfs_metadata 64 报错 官方说明&#xff1a; 元数据池通常最多可容纳几 GB 的数据。为…

Web后端开发_01

Web后端开发 请求响应 SpringBoot提供了一个非常核心的Servlet 》DispatcherServlet&#xff0c;DispatcherServlet实现了servlet中规范的接口 请求响应&#xff1a; 请求&#xff08;HttpServletRequest&#xff09;&#xff1a;获取请求数据响应&#xff08;HttpServletRe…

正点原子嵌入式linux驱动开发——Linux DAC驱动

上一篇笔记中学习了ADC驱动&#xff0c;STM32MP157 也有DAC外设&#xff0c;DAC也使用的IIO驱动框架。本章就来学习一下如下在Linux下使用STM32MP157上的DAC。 DAC简介 ADC是模数转换器&#xff0c;负责将外界的模拟信号转换为数字信号。DAC刚好相反&#xff0c;是数模转换器…

MS512非接触式读卡器 IC

MS512 是一款应用于 13.56MHz 非接触式通信中的高集 成度读写卡芯片。它利用了先进的调制和解调技术&#xff0c;完全集 成了在 13.56MHz 下的各种非接触式通信方式和协议。 主要特点  高度集成的解调和解码模拟电路  采用少量外部器件&#xff0c;即可将输出驱动级接…

# Spring事务与分布式事务

一、事务的具体定义 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元&#xff0c;组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交&#xff0c;只要其中任一操作执行失败&#xff08;出现异常&#xff09;&#xff0c;都将导致整个事务…

联想笔记本Fn + A可以全选,Ctrl失效

问题&#xff1a;联想笔记本Fn A可以全选&#xff0c;ctrl失效。 原因&#xff1a;BIOS启用了Fn键和Ctrl键互换。 解决操作&#xff1a; 1.开机时一直按F2&#xff0c;进入BIOS 2.点击More Settings > 2.选取Configuration 3.将Fool Proof Fn Ctrl 设定变更为Disabled 4.按…

【Linux】进程概念IV 进程地址空间

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 本篇导航 0. 数据在内存中的分布1. 虚拟地址与真实物理地址2. 进程地址空间2.1 进程地址空间概念2.2 进程->页表->内存 0. 数据在内…

MASK、MPSK、MFSK信号的调制与解调+星座图

MASK、MPSK、MFSK信号的调制与解调星座图 本文主要涉及多进制幅度键控&#xff08;MASK&#xff09;、多进制相移键控&#xff08;MPSK&#xff09;、多进频移键控&#xff08;MFSK&#xff09;的调制与解调&#xff0c;同时涉及到星座图的分析。 关于通信原理还有其他文章可参…

【SpringBoot整合JSP】

【源码】SpringBoot整合JSP 一、前言二、创建web项目,webapp 【创建视图层】&#xff08;一&#xff09;在 main 目录下相关目录1. 点击 “FIle”-> “Project Structure”&#xff0c;选择 “Model”-> “Web”&#xff0c;将“Web Resource Directory”的路径修改为 刚…

JOSEF约瑟 反时限过流继电器JGL-115板前接线5A速断保护

系列型号 JGL-111反时限过流继电器&#xff1b;JGL-112反时限过流继电器&#xff1b; JGL-113反时限过流继电器&#xff1b;JGL-114反时限过流继电器&#xff1b; JGL-115反时限过流继电器&#xff1b;JGL-116反时限过流继电器&#xff1b; JGL-117反时限过流继电器&#xff1b…