【数据结构】AVL树

news2024/11/24 11:25:41

AVL树

  • 一、AVL树的概念
  • 二、AVL的接口
    • 2.1 插入
    • 2.2 旋转
      • 2.2.1 左单旋
      • 2.2.2 右单旋
      • 2.2.3 左右双旋
      • 2.2.4 右左双旋
  • 三、验证
  • 四、源码

一、AVL树的概念

当我们用普通的搜索树插入数据的时候,如果插入的数据是有序的,那么就退化成了一个链表,搜索效率低下。

为了应对这种情况,就出现了AVL树(高度平衡二叉搜索树):

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1

AVL树的性质:

  • 它的左右子树都是AVL树。
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1

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

在这里插入图片描述
平衡因子是用来检测树的状态,如果平衡因子都在(-1, 0, 1)中,则没问题,反之则需要调整。

二、AVL的接口

AVL的节点定义:

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

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

2.1 插入

AVL的基本插入流程跟搜索树相似,但是AVL树多了一个平衡因子。
一旦插入新节点,就要往上更新平衡因子

  • 如果是在左边点插入,则平衡因子--
  • 如果是在右边点插入,则平衡因子++

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
更新一个结点之后我们需要去进行判断,子树的高度是否发生了变化:

1️⃣ 当父节点的平衡因子变成0:说明原来是-1或1,那么也就是把矮的地方填平了,父节点所在树的高度不变,不需要继续更新
2️⃣ 当父节点的平衡因子变成1或-1:说明原来是0,父节点所在树的高度发生变化,需要继续更新
3️⃣ 当当父节点的平衡因子变成2或-2违反规则,需要进行旋转处理

所以我们可以利用parent节点(插入之前的叶子节点),从它开始往上更新。

bool insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else return false;
	}
	cur = new Node(kv);
	if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	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)
			{
				// 左右双旋
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				// 右左双旋
				RotateRL(parent);
			}
			break;
		}
		else
		{
			cout << "结构出错" << endl;
			assert(false);
		}
	}
	return true;
}

2.2 旋转

为了保证每个结点的左右子树高度之差的绝对值不超过1,所以当平衡因子变为-2或2时,需要旋转来保持平衡。
旋转规则:
1️⃣ 让这颗子树左右高度差不超过1
2️⃣ 旋转过程中继续保持它是搜索树
3️⃣ 更新调整孩子节点的平衡因子
4️⃣ 让这颗子树的高度根插入前保持一致

2.2.1 左单旋

二叉树的结构有无数种情况,所以我们需要总结出抽象图来分析
在这里插入图片描述
解释:
a/b/c是高度为h的AVL树。

新节点插入较高右子树的右侧—右右:左单旋
在这里插入图片描述
左单旋的步骤:

1️⃣ 20的左边调整到10的右边
2️⃣ 10变成20的左边,20做根
3️⃣ 把平衡因子变为0

在这里插入图片描述

void RotateL(Node* parent)
{
	Node* top = parent->_parent;
	Node* right = parent->_right;
	// 20的左边调整到10的右边
	parent->_right = right->_left;
	if (right->_left) right->_left->_parent = parent;
	// 10变成20的左边,20做根
	right->_left = parent;
	parent->_parent = right;
	if (top)// 子树
	{
		if (parent == top->_left) top->_left = right;
		else top->_right = right;
		right->_parent = top;
	}
	else// 完整的树
	{
		_root = right;
		_root->_parent = nullptr;
	}
	// 更新平衡因子
	parent->_bf = right->_bf = 0;
}

2.2.2 右单旋

新节点插入较高左子树的左侧—左左:右单旋
在这里插入图片描述在这里插入图片描述

void RotateR(Node* parent)
{
	Node* top = parent->_parent;
	Node* left = parent->_left;
	Node* leftR = left->_right;
	parent->_left = leftR;
	if (leftR) leftR->_parent = parent;
	left->_right = parent;
	parent->_parent = left;
	if (top)
	{
		if (parent == top->_left) top->_left = left;
		else top->_right = left;
		left->_parent = top;
	}
	else
	{
		_root = left;
		_root->_parent = nullptr;
	}
	parent->_bf = left->_bf = 0;
}

2.2.3 左右双旋

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

我们看到上面的单旋,我们会想,如果是这么插入呢?
在这里插入图片描述

其实这个图可以转化为:

在这里插入图片描述
先以10为轴进行左单旋,这样就把“折线”变成了直线,在以20为轴进行右单旋。
在这里插入图片描述
这里就要注意平衡因子的更新
15的平衡因子为0
但是其他两个会有三个不同的情况:

1️⃣ 当right的平衡因子为-1时(插入在b),双旋结束后parent、left、right的平衡因子分别更新为1、0、0
在这里插入图片描述
2️⃣ 当right的平衡因子为1时(插入在c),双旋结束后parent、left、right的平衡因子分别更新为0、-1、0
在这里插入图片描述
3️⃣ 当right的平衡因子为0时,双旋后parent、left、right的平衡因子分别更新为0、0、0
在这里插入图片描述

所以在旋转前要先进行判断在哪插入(通过平衡因子),旋转后手动更新即可。

void RotateLR(Node* parent)
{
	Node* left = parent->_left;
	Node* right = left->_right;
	int bf = right->_bf;// 提前记录
	RotateL(parent->_left);
	RotateR(parent);
	if (bf == -1)// 左子树新增
	{
		left->_bf = 0;
		right->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)// 右子树新增
	{
		left->_bf = -1;
		right->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 0)// 自己就是新增
	{
		left->_bf = right->_bf = parent->_bf = 0;
	}
	else assert(false);
}

2.2.4 右左双旋

	void RotateRL(Node* parent)
	{
		Node* right = parent->_right;
		Node* left = right->_left;
		int bf = left->_bf;
		RotateR(right);
		RotateL(parent);
		if (bf == -1)
		{
			parent->_bf = 0;
			left->_bf = 0;
			right->_bf = 1;
		}
		else if (bf == 1)
		{
			right->_bf = 0;
			left->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			left->_bf = right->_bf = parent->_bf = 0;
		}
		else assert(false);
	}

三、验证

为了验证是否为二叉搜索树,我们可以先写一个中序遍历

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

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

void Inorder()
{
	_Inorder(_root);
}

为了验证是否为AVL树,我们要让每个节点的左右子树高度的绝对值差小于等于1。

int Height(Node* root)
{
	if (!root)
	{
		return 0;
	}
	int lh = Height(root->_left) + 1;
	int rh = Height(root->_right) + 1;
	return max(lh, rh);
}

bool IsBalance(Node* root)
{
	if (!root)
	{
		return true;
	}
	int lh = Height(root->_left);
	int rh = Height(root->_right);
	if (rh - lh != root->_bf)
	{
		cout << root->_kv.first << ":";
		cout << root->_bf << ":";
		cout << "平衡因子出错" << endl;
		return false;
	}
	if (abs(rh - lh) > 1)
	{
		return false;
	}
	return IsBalance(root->_left) && IsBalance(root->_right);
}

bool IsBalance()
{
	return IsBalance(_root);
}

我们可以用大量的随机值来测定:

void test()
{
	const int N = 100000;
	AVLTree<int, int> tt;
	srand(time(0));
	for (int i = 0; i < N; i++)
	{
		int x = rand();
		tt.insert(make_pair(x, x));
	}
	//tt.Inorder();
	cout << tt.IsBalance() << endl;
}

四、源码

#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <cstdlib> 

using namespace std;

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

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


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

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else return false;
		}
		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		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)
				{
					// 左右双旋
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					// 右左双旋
					RotateRL(parent);
				}
				break;
			}
			else
			{
				cout << "结构出错" << endl;
				assert(false);
			}
		}
		return true;
	}

	void RotateL(Node* parent)
	{
		Node* top = parent->_parent;
		Node* right = parent->_right;
		// 20的左边调整到10的右边
		parent->_right = right->_left;
		if (right->_left) right->_left->_parent = parent;
		// 10变成20的左边,20做根
		right->_left = parent;
		parent->_parent = right;
		if (top)// 子树
		{
			if (parent == top->_left) top->_left = right;
			else top->_right = right;
			right->_parent = top;
		}
		else// 完整的树
		{
			_root = right;
			_root->_parent = nullptr;
		}
		// 更新平衡因子
		parent->_bf = right->_bf = 0;
	}

	void RotateR(Node* parent)
	{
		Node* top = parent->_parent;
		Node* left = parent->_left;
		Node* leftR = left->_right;
		parent->_left = leftR;
		if (leftR) leftR->_parent = parent;
		left->_right = parent;
		parent->_parent = left;
		if (top)
		{
			if (parent == top->_left) top->_left = left;
			else top->_right = left;
			left->_parent = top;
		}
		else
		{
			_root = left;
			_root->_parent = nullptr;
		}
		parent->_bf = left->_bf = 0;
	}

	void RotateLR(Node* parent)
	{
		Node* left = parent->_left;
		Node* right = left->_right;
		int bf = right->_bf;// 提前记录
		RotateL(left);
		RotateR(parent);
		if (bf == -1)// 左子树新增
		{
			left->_bf = 0;
			right->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)// 右子树新增
		{
			left->_bf = -1;
			right->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 0)// 自己就是新增
		{
			left->_bf = right->_bf = parent->_bf = 0;
		}
		else assert(false);
	}

	void RotateRL(Node* parent)
	{
		Node* right = parent->_right;
		Node* left = right->_left;
		int bf = left->_bf;
		RotateR(right);
		RotateL(parent);
		if (bf == -1)
		{
			parent->_bf = 0;
			left->_bf = 0;
			right->_bf = 1;
		}
		else if (bf == 1)
		{
			right->_bf = 0;
			left->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			left->_bf = right->_bf = parent->_bf = 0;
		}
		else assert(false);
	}

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

		_Inorder(root->_left);
		cout << root->_kv.first << "<=>" << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	void Inorder()
	{
		_Inorder(_root);
	}

	int Height(Node* root)
	{
		if (!root)
		{
			return 0;
		}
		int lh = Height(root->_left) + 1;
		int rh = Height(root->_right) + 1;
		return max(lh, rh);
	}

	bool IsBalance(Node* root)
	{
		if (!root)
		{
			return true;
		}
		int lh = Height(root->_left);
		int rh = Height(root->_right);
		if (rh - lh != root->_bf)
		{
			cout << root->_kv.first << ":";
			cout << root->_bf << ":";
			cout << "平衡因子出错" << endl;
			return false;
		}
		if (abs(rh - lh) > 1)
		{
			return false;
		}
		return IsBalance(root->_left) && IsBalance(root->_right);
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}
private:
	Node* _root = nullptr;
};

void test()
{
	const int N = 100000;
	AVLTree<int, int> tt;
	srand(time(0));
	for (int i = 0; i < N; i++)
	{
		int x = rand();
		tt.insert(make_pair(x, x));
	}
	//tt.Inorder();
	cout << tt.IsBalance() << endl;
}

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

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

相关文章

纵然是在产业互联网的时代业已来临的大背景下,人们对于它的认识依然是短浅的

纵然是在产业互联网的时代业已来临的大背景下&#xff0c;人们对于它的认识依然是短浅的。这样一种认识的最为直接的结果&#xff0c;便是我们看到了各式各样的产业互联网平台的出现。如果一定要找到这些互联网平台的特点的话&#xff0c;以产业端为出发点&#xff0c;无疑是它…

嵌入式:UCOS移植+简单创建任务

目录 一、UCOS操作系统 二、UCOS移植 1、文件介绍 2、UCOS || 源码分析 3、打开Software文件 三、UCOS任务创建 一、UCOS操作系统 C/OS 是 Micrium 公司出品的实时操作系统&#xff0c; C/OS 目前有两个版本&#xff1a; C/OS-II 和 C/OS-III。 C/OS 是一种基于优先级…

Linux GPIO 开发指南

文章目录Linux GPIO 开发指南1 概述1.1 编写目的1.2 适用范围1.3 相关人员2 模块介绍2.1 模块功能介绍2.2 相关术语介绍2.3 总体框架2.4 state/pinmux/pinconfig2.5 源码结构介绍3 模块配置3.1 kernel menuconfig 配置3.2 device tree 源码结构和路径3.2.1 device tree 对 gpio…

Python计算 -- 内附蓝桥题:相乘

计算 ~~不定时更新&#x1f383;&#xff0c;上次更新&#xff1a;2023/02/23 &#x1f5e1;常用函数&#xff08;方法&#xff09; 1. 求一个整数的最末位 举个栗子&#x1f330; n int(input()) end n % 10蓝桥例题1 - 相乘✖️ 题目描述 本题为填空题&#xff0c;…

MySQL 11:MySQL锁

锁是一种机制&#xff0c;计算机通过这种机制协调多个进程或线程对资源的并发访问&#xff08;以避免争用&#xff09;。在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争夺外&#xff0c;数据也是一种被众多用户共享的资源。如何保证并…

叠氮炔点击化学634926-63-9,Propargyl-PEG1-NHBoc,氨基叔丁酯PEG1丙炔基相关性质分享

●外观以及性质&#xff1a;Propargyl-PEG1-NHBoc产物呈固体或粘性液体&#xff0c;取决于PEG分子量&#xff0c;包含1个丙炔基和一个氨基叔丁酯&#xff0c;炔丙基可通过铜催化的叠氮炔点击化学与含叠氮化合物或生物分子反应&#xff0c;以产生稳定的三唑键&#xff0c;带有 P…

Windows下载安装Redis的详细步骤

目录 一、概述 1.redis的版本维护介绍 2.msi安装包和压缩包的优点和缺点 二、操作步骤 三、测试是否安装成功&#xff08;查看版本&#xff09; 四、获取资源 一、概述 1.redis的版本维护介绍 Redis的官网只提供Linux系统的下载。但是微软的技术团队长期开发和维护着这…

Tina_Linux_启动优化_开发指南

文章目录Tina_Linux_启动优化_开发指南1 概述2 启动速度优化简介2.1 启动流程2.2 测量方法2.2.1 printk time2.2.2 initcall_debug2.2.3 bootgraph.2.2.4 bootchart2.2.5 gpio 示波器.2.2.6 grabserial.2.3 优化方法2.3.1 boot0启动优化2.3.1.1 非安全启动.2.3.1.2 安全启动2.3…

jmeter接口自动化测试框架

接口测试可以分为两部分&#xff1a; 一是线上接口&#xff08;生产环境&#xff09;自动化测试&#xff0c;需要自动定时执行&#xff0c;每5分钟自动执行一次&#xff0c;相当于每5分钟就检查一遍线上的接口是否正常&#xff0c;有异常能够及时发现&#xff0c;不至于影响用…

易点易动助力企业固定资产信息化管理

对于生产制造或者互联网企业而言&#xff0c;固定资产比重较高&#xff0c;是企业资产的大头&#xff0c;一些办公设备、生产设备数量和金额都比较大。提升企业固定资产管理水平&#xff0c;是企业实现信息化建设的必要条件。 目前&#xff0c;国内的很多企业在固定资产管理中…

零售航母沃尔玛公布业绩:喜忧参半

2月21日美股盘前&#xff0c;零售巨无霸沃尔玛公布了截至1月的2023财年第四季度业绩报告。财报中不乏可圈可点之处&#xff0c;但是利润迎来六年首降&#xff0c;新财年的利润指引要也比预期低很多&#xff0c;可以说喜忧参半。 一、Q4业绩可圈可点 营收方面&#xff1a;在本…

漏斗分析法

一什么是漏斗分析&#xff1f; 漏斗分析是数据领域最常见的一种“程式化”数据分析方法&#xff0c;它能够科学地评估一种业务过程&#xff0c;从起点到终点&#xff0c;各个阶段的转化情况。通过可以量化的数据分析&#xff0c;帮助业务找到有问题的业务环节&#xff0c;并进…

插画网课平台排名

插画网课平台哪个好&#xff0c;插画网课排名靠前的有哪些&#xff0c;今天给大家梳理了国内5家专业的插画网课平台&#xff0c;各有优势和特色&#xff0c;给学插画的小伙伴提供选择&#xff0c;报插画网课一定要选择靠谱的&#xff0c;否则人钱两空泪两行&#xff01; 一&am…

使用 xshell 远程连接(使用 xftp 远程传输)

xshell 和 xftp的使用都基于ssh协议&#xff0c;我们需要先在远程服务端或者虚拟机上安装ssh服务&#xff0c;然后才能远程连接。 目录 1、什么是ssh协议&#xff1f; 2、安装 openssh (1) 安装 openssh 服务器 (2) 关闭服务器防火墙&#xff08;或者开放端口22&#xff09…

四个步骤在CRM系统中设置游戏化机制

长期高强度的单一工作会让销售人员逐渐失去对工作的兴趣&#xff0c;导致销售状态缺少动力和激情&#xff0c;工作开展愈加困难。不少企业通过CRM销售管理系统设置游戏化竞赛&#xff0c;调动销售人员的工作积极性。那么&#xff0c;如何在CRM系统中设置游戏化机制&#xff1f;…

深入浅出 MySQL 索引(一)

MySQL 索引&#xff08;基础篇&#xff09; 你好&#xff0c;我是悟空。 本文目录如下&#xff1a; 一、前言 最近在梳理 MySQL 核心知识&#xff0c;刚好梳理到了 MySQL 索引相关的知识&#xff0c;我的文章风格很多都是原理 实战的方式带你去了解知识点&#xff0c;所以…

Spring+MVC+MYbatis注解开发

Spring常见注解 注解一&#xff1a;Configuration 用在类上面&#xff0c;加上这个注解的类可以成为一个spring的xml配置文件&#xff0c;使用的是java代码的配置 注解二&#xff1a;ComponentScan 用在类上&#xff0c;加上注解可以指定扫描路径 注解三&#xff1a;创建对…

Uni-app使用vant和uview组件

目录 1.安装vant组件 1.1安装前需知 1.2.安装 1.3.创建uni-app项目 2.安装uview-ui组件 2.1官网 2.2安装 2.3安装成功 1.安装vant组件 1.1安装前需知 小程序能使用vant-weapp组件&#xff0c;且官网的安装是直接导入小程序中&#xff0c;不能直接导入uni-app框架中 V…

Python的标准模块介绍:sys、os、random和time

Python内置了许多标准模块&#xff0c;例如sys、os、random和time模块等&#xff0c;下面为大家介绍几个常用的标准模块。 1.sys模块 sys模块中提供了一系列与Python解释器交互的函数和变量&#xff0c;用于操控Python的运行时环境。sys模块中常用变量与函数如表1所示。 表1…

Apifox = Postman + Swagger + Mock + JMeter

目录 可视化API设计 高效 & 零学习成本 可复用的“数据模型” 遵循 OpenAPI(Swagger) 规范 可导入 Swagger 等 20 数据格式 具体使用尝鲜 多项目管理 支持多环境切换 支持IDEA、浏览器、桌面应用 Idea插件 公共API hub库 如题&#xff1a;一款非常好用的API管理测…