<数据结构>AVL树详解

news2024/11/25 14:20:18

目录

AVL是什么?

平衡因子

旋转

左单旋

右单旋

左右双旋

右左双旋

全部的代码


AVL树又叫平衡树(Balance_Tree),笔者认为这个名字非常不错,中国嘛,就是在意阴阳平衡,之前小编还拿这个当过自己网络IP,哈哈哈。

AVL是什么?

本文适合有一点点数据结构基础的来看,简单的树形结构知道吧,也就是一堆根和结点的集合,那它有啥用呢?我们一般用树形结构来做什么呢?说这个之前,我们不妨先来看看其他的数据结构的作用

链表:它是链式结构,单向和双向两种结构,能做到简单的数据插入和删除,且效率不算低,时间复杂度 O(1),查找数据时间复杂度 O(N)

栈:满足先入后出的特性,不支持查找

队列:满足先入先出的特性,不支持查找

顺序表:线性表结构,能做到简单的插入删除,效率相比链表低了些,时间复杂度O (N),查找数据的时间复杂度 O(N)

上面列举的几个结构,我们将其相互对比一下,最理想的也就是插入可以O(1),但是查找是O(N),那如果我们还想要再优化呢,这里大家去了解一下二叉搜索树,很简单的一个用于数据搜索的树形结构,它的插入是O(1),数据查询是有条件达到O(logN)的,但是由于没有任何的优化算法,所以搜索树可能会出现左右子树的高度差太大了,甚至数据都在一边的极端情况,这时的查找复杂度又变为了O(N),我们这里介绍的AVL树就是在二叉搜索树的基础上进行了优化调整,结果是控制每一个根结点的左右子树的高度差不超过1

上面说所的高度差不超过1,也是AVL树的平衡由来

平衡因子

为了便于我们控制平衡的算法,我们引入一个平衡因子 bf 进来,并制定规则:新插入的结点在左子树 bf--,在右结点 bf++

template<class K, class V>
struct AVLTreeNode
{
	// 三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	std::pair<K, V> _kv; // 数据
	int _bf; // 平衡因子

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

	}
};

这样我们可以直接通过平衡因子的数值来反映该结点是否已经平衡了。AVL树要保证的就是每一棵子树都是平衡的,最后总体也就平衡了。如果 bf == 0 说明左右子树高度相等,bf == 1 说明右子树比左子树高一层,bf == -1 说明左子树比右子树高一层,否则其他情况都是不符合平衡的

说到这,最重要的接口就是Insert插入接口的设计了

平衡树的数据都是(K,V)结构的,我们采用pair来放有效数据,我们将需要的数据传入接口时,我们第一步要做的就是找到我们待插入的位置,延续搜索二叉树的特点,为了方便后面left就是左子树,right就是右子树,root就是根结点,parent就是父节点

规定:left < root < right

直接和传入的kv值比较查询即可

		while (cur)
		{
			// 找到待插入叶子 规定:left < root < right
			parent = cur;
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

找到后在判断该位置是parent的left还是right插入即可

// 找到后开始插入
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

正常插入后,便是关键的一步,更新控制平衡因子!

就和上面说的一样,从插入这里的parent开始逐步向上更新,插入的是left就是parent->_bf--,right就是++,之后在判断 parent->_bf 的值,如下

如果 bf == 0 说明左右子树高度相等,bf == 1 说明右子树比左子树高一层,bf == -1 说明左子树比右子树高一层,否则其他情况都是不符合平衡的,则需要做旋转的处理,下面的函数我们后续详细解释,这个是精髓!!!

// 更新平衡因子
        while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else if (cur == parent->_left)
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				// 说明parent所在的子树已经不平衡了,需要旋转处理
				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);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				// 树出问题了
				assert(false);
			}
		}

上面代码的框架就完成了80%了,剩下的就只需要将其20%的旋转细节处理完就可以了,这里看到有左旋,右旋,左右旋转,右左旋转,是什么意思呢?我们看下图

 我们看到左边的cur插入后,8结点的 bf == 2了,不满足平衡了,我们做了一次旋转处理后,在不改变 left < root < right 这个条件的前提下,又让了 left 和 right 的高度差 == 0 了,这就是一次旋转处理,接下来我们只需要将所有的旋转情况完善即可。

旋转

左单旋

 左单旋处理的情况,图中见,parent的右子树的右子树插入结点后破坏了平衡,我们进行一次左单旋后,变成右边的样子,重新恢复平衡,左旋含义即将这里的30从左边上旋转到了左下来

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

		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		parent->_parent = subR;
		subR->_left = parent;
		
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = pNode;

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

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

右单旋

和上面的左单旋情况类似

 右单旋的含义就是右上的60旋转到右下来了

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

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

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

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = pNode;
			if (pNode->_left == parent)
			{
				pNode->_left = subL;
			}
			else
			{
				pNode->_right = subL;
			}
		}

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

左右双旋

 这里的情况稍微复杂了一点,左单旋和右单旋都是处理的要么是右边的增加,要么是左边的增加,双旋处理的是中间子树增加,这里中间子树的增加又可以分为两种情况,加在左子树和加在右子树

从这里的图中看出,现在处理的事加在左子树的情况,注意这里的左右子树是90结点的左右子树,图中也可以看出,60结点插入在它的left和right的处理情况相同,唯一的差别就是处理后,需要区别的处理一下平衡因子的更新

甚至我们可以想一个极端情况,下面看到依然是不影响的

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

		RotateL(subL);
		RotateR(parent);

		// 左子树 or 右子树
		if (bf == -1)
		{
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
		subLR->_bf = 0;
	}

右左双旋

和左右双旋类似,这里新结点是插入在了parent的右边中间

 我们根据图中的结构变化来处理结点之间的关系即可,先将90右旋,再将30左旋

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

		// 左子树 or 右子树
		if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
		subRL->_bf = 0;
	}

全部的代码

#pragma once

#include <iostream>
#include <map>
#include <cassert>

using std::pair;
using namespace std;

/*
	1.定义AVL节点类型
	2.定义AVL树类型
*/

// AVLNode
template<class K, class V>
struct AVLTreeNode
{
	// 三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	std::pair<K, V> _kv; // 数据
	int _bf; // 平衡因子

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

	}
};

// AVLTree
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 _root;
		}

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			// 找到待插入叶子 规定:left < root < right
			parent = cur;
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		// 找到后开始插入
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else if (cur == parent->_left)
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				// 说明parent所在的子树已经不平衡了,需要旋转处理
				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);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				// 树出问题了
				assert(false);
			}
		}

		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		return;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		int diff = rightHT - leftHT;

		cout << root->_kv.first << "-diff:" << diff << " bf:" << root->_bf << " " << endl;

		if (diff != root->_bf)
		{
			cout << "diff:" << diff << " bf:" << root->_bf << " ";
			cout << root->_kv.first << ":平衡因子异常" << endl;
			return false;
		}

		return abs(leftHT - rightHT) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

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

		return max(Height(root->_left), Height(root->_right)) + 1;
	}

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

		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		parent->_parent = subR;
		subR->_left = parent;
		
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = pNode;

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

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

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

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

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

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = pNode;
			if (pNode->_left == parent)
			{
				pNode->_left = subL;
			}
			else
			{
				pNode->_right = subL;
			}
		}

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

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

		RotateL(subL);
		RotateR(parent);

		// 左子树 or 右子树
		if (bf == -1)
		{
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
		subLR->_bf = 0;
	}

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

		// 左子树 or 右子树
		if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
		subRL->_bf = 0;
	}

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

		_InOrder(root->_left);
		std::cout << root->_kv.first << ":" << root->_kv.second << std::endl;
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

void AVLTest1()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}

	t1.InOrder();

	cout << "IsBalance:" << t1.IsBalance() << endl;

	// 测试平衡因子是否异常
	int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t2;
	for (auto e : b)
	{
		t2.Insert(make_pair(e, e));
	}


	t2.InOrder();

	cout << "IsBalance:" << t2.IsBalance() << endl;
}

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

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

相关文章

计算机组成原理之计算机系统概述(补充)

目录 一、引入二、计算机发展历程2.1 什么是计算机系统2.2 硬件的发展2.3 软件的发展2.4 目前的发展趋势2.5 小结 三、计算机硬件的基本组成3.1 早期冯诺依曼机的结构3.2 现代计算机的结构3.3 小结 四、各个硬件的工作原理4.1 主存储器的基本构成4.2 小结 五、计算机系统的层次…

常见项目管理中npm包操作总结

前言 我们在日常工作中&#xff0c;可能需要下载包、创建包、发布包等等。本篇推文将记录日常项目中关于npm包的操作。 引用包 npm仓库公开的包我们都可以通过npm install的命令进行引用下载。 而我们开发的业务公共组件需要在公司内部项目公共引用&#xff0c;而不希望公开为外…

Java“牵手”京东店铺所有商品API接口数据,通过店铺ID获取整店商品详情数据,京东店铺所有商品API申请指南

京东平台店铺所有商品数据接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取京东整店的商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片、价格信息等详细信息 。 获取店铺所有商品接口API是一种用于获取电商平台上商品详…

狂神汇编原理

1.汇编(可以破解软件,写外挂…) ------计算器语言----- 2.机器语言 0和1 c>汇编—>二进制 INC --抽象-->编译器 0100 0000DECMULDIV3.进制 10进制(10个手指) 可以自己定义进制,作为进制加密解密,查数 1进制 一进一 写出2 ->1 1 2进制 0 1 10 11 100 101 110…

使用Arrays.asList生成的List集合,操作add方法报错

早上到公司&#xff0c;刚到工位&#xff0c;测试同事就跑来说"功能不行了&#xff0c;报服务器异常了&#xff0c;咋回事";我一脸蒙&#xff0c;早饭都顾不上吃&#xff0c;要来了测试账号复现了一下&#xff0c;然后仔细观察测试服务器日志&#xff0c;发现报了一个…

springboot实战(四)之整合mybatis-plus

目录 环境&#xff1a; 准备&#xff1a; 开始&#xff1a; 1.创建表t_user 2.项目添加依赖 3.配置 1.配置mysql链接信息 2.在启动类配置mapper扫描路径 4.创建实体类 5.创建mapper 6.测试 环境&#xff1a; jdk&#xff1a;1.8 springboot版本&#xff1a;2.7.15…

3D虚拟数字人定制+AI交互数字人技术,助力企业开启营销新思路

近日&#xff0c;番茄小说推出数字人IP番卷卷&#xff0c;其承担着连接现实世界与番茄世界的重要角色&#xff0c;作为用户进入番茄世界的数字导游。数字人番卷卷的出现&#xff0c;一方面能够强化品牌在用户层面的心智&#xff0c;另一方面可以让用户拥有多层次、多情感、角色…

C++ while 循环

只要给定的条件为真&#xff0c;while 循环语句会重复执行一个目标语句。 语法 C 中 while 循环的语法&#xff1a; while(condition) {statement(s); }在这里&#xff0c;statement(s) 可以是一个单独的语句&#xff0c;也可以是几个语句组成的代码块。condition 可以是任意…

防雷接地+防雷检测综合应用解决方案

防雷接地和防雷检测是防雷工程中的重要内容&#xff0c;它们旨在保护建筑物和设备免受雷电的危害。地凯科技将介绍防雷接地和防雷检测的基本原理、施工案例方案和国标措施。 防雷接地是指将建筑物的金属结构、防雷装置和电气设备与地面连接&#xff0c;形成一个接地系统&#…

vscode宏键绑定

开发语言php 实现输入[ 得到 [];的效果 [win]ctrlp,[mac]superp 输入>keyboard 选择 在json文件里增加(目前有缺陷,sublime的设置是比较完美的.或者phpstorm默认不需要配置): {"key": "[","command": "editor.action.insertSnippet&…

CUDA Toolkit多版本安装与配置

CUDA Toolkit多版本安装目的是为了将CUDA Toolkit支持多个版本&#xff0c;并将当前版本更新到后续支持常见pytorch的版本&#xff08;即cuda11.6&#xff09;&#xff0c;目前该系统默认安装的是cuda10.2&#xff0c;cuda11.0和cuda11.2。CUDA一般有两种API&#xff0c;一个是…

PMD代码检查:没有使用的私有字段(UnusedPrivateField)

https://docs.pmd-code.org/pmd-doc-6.55.0/pmd_rules_java_bestpractices.html#unusedprivatefield 如果代码中的private字段声明了、或者赋值了&#xff0c;但没有被用到&#xff0c;就报违反项。例如&#xff1a; 但是从PMD 6.50.0版本开始&#xff0c;如果私有字段上有任…

h5网站开发,页面加载wow.js动画时,出现了左右滚动条,怎么解决?

一、问题描述&#xff1a; 如下图所示&#xff0c;页面在加载WOW动画时出现了左右滚动条&#xff1a; 二、解决方法&#xff1a; 使用CSS样式来隐藏滚动条 在CSS文件中添加以下样式&#xff1a; body {overflow-x: hidden; /* 隐藏水平滚动条 */ }完美解决&#xff0c;还不会…

智慧电力方案:安防监控/视频分析/智能分析网关AI识别技术在电力领域中的应用

一、行业痛点 随着经济的飞速发展&#xff0c;电力已经是人们生活中必不可少的&#xff0c;无论是在生活还是工作中&#xff0c;电的存在都是不可或缺的。但电力的高效运维&#xff0c;一直是一个难题&#xff0c;当前普通的电力运维系统已无法满足人们的管理需求&#xff0c;…

什么是帧呢

在处理图片时&#xff0c;经常听到帧的概念&#xff0c;什么是帧呢&#xff1f; 概念 帧就是一幅静止的画面。 1、帧率&#xff08;Frame rate&#xff09;是称为帧的位图图像连续出现在显示器上的频率&#xff08;速率&#xff09;&#xff0c;就是每秒有多少帧。 2、帧就是…

线性代数的学习和整理17:向量空间的基,自然基,基变换等(未完成)

目录 3 向量空间的基&#xff1a;矩阵的基础/轴 3.1 从颜色RGB说起 3.2 附属知识 3.3 什么样的向量可以做基&#xff1f; 3.4 基的分类 3.1.1 不同空间的基---向量组的数量可能不同 3.1.2 自然基 3.1.3 正交基 3.1.4 标准正交基 3.1.5 基和向量/矩阵 3.1.6 基变换 …

信息技术01--初/高中--选择真题汇总(197道题)

文章目录 1 真题 01-102 真题 11-203 真题 21-304 真题 31-405 真题 41-506 真题 51-607 真题 61-708 真题 71-809 真题 81-9010 真题 91-10011 真题 101-11012 真题 111-12013 真题 121-13014 真题 131-14015 真题 141-15016 真题 151-16017 真题 161-17018 真题 171-18019 真…

【Kali Linux高级渗透测试】深入剖析Kali Linux:高级渗透测试技术与实践

&#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于恒川的日常汇报系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏C语言初阶、C…

山西电力市场日前价格预测【2023-09-02】

日前价格预测 预测明日&#xff08;2023-09-02&#xff09;山西电力市场全天平均日前电价为304.03元/MWh。其中&#xff0c;最高日前电价为373.15元/MWh&#xff0c;预计出现在19: 15。最低日前电价为191.94元/MWh&#xff0c;预计出现在12: 30。 价差方向预测 1&#xff1a; 实…

FPGA时序分析与约束(1)——组合电路时序

写在最前面&#xff1a; 关于时序分析和约束的学习似乎是学习FPGA的一道分水岭&#xff0c;似乎只有理解了时序约束才能算是真正入门了FPGA&#xff0c;对于FPGA从业者或者未来想要从事FPGA开发的工程师来说&#xff0c;时序约束可以说是一道躲不过去的坎&#xff0c;所以从这篇…