数据结构 - AVL树

news2025/2/23 7:33:21

文章目录

    • 一、AVL树的介绍
    • 二、AVL树的实现
      • 1、基本框架
      • 2、查找
      • 3、插入
      • 4、删除
      • 5、测试
      • 6、总代码
    • 三、AVL树的性能


一、AVL树的介绍

1、概念

AVL树(Adelson-Velsky and Landis Tree)是一种自平衡的二叉搜索树。它得名于其发明者G. M. Adelson-Velsky和E. M. Landis。在AVL树中,任何节点的两个子树的高度最大差别为1,这保证了树的平衡性,从而避免了在极端情况下(如数据有序或接近有序时)二叉搜索树退化为链表,导致操作效率低下的问题。

在这里插入图片描述

2、特点

(1)平衡性:AVL树通过维护每个节点的平衡因子(左子树高度与右子树高度之差)来保持树的平衡。平衡因子的值只能是-1、0或1。如果某个节点的平衡因子绝对值大于1,那么该树就失去了平衡,需要通过旋转操作来重新平衡。

(2)旋转操作:当AVL树失去平衡时,会触发旋转操作来恢复平衡。旋转操作主要有四种:右旋(单旋)、左旋(单旋)、右-左双旋和左-右双旋。这些旋转操作通过改变树中节点的链接关系来降低树的高度,从而保持树的平衡。

(3) 高效的查找、插入和删除操作:由于AVL树保持了平衡,其查找、插入和删除操作的时间复杂度都能保持在O(log
n)的范围内,其中n是树中节点的数量。这使得AVL树在处理大量数据时能够保持高效的性能。

(4)空间开销:与普通的二叉搜索树相比,AVL树需要额外的空间来存储每个节点的平衡因子。这增加了树的空间开销,但在大多数情况下,这种开销是可以接受的。

3、应用场景

AVL树广泛应用于需要频繁插入、删除和查找操作的场景,如数据库索引、文件系统的目录结构、实时数据更新等。在这些场景中,AVL树能够保持高效的性能,确保数据处理的快速响应。

4、缺点

尽管AVL树具有许多优点,但它也有一些缺点。例如,在每次插入或删除节点后都需要进行平衡检查和可能的旋转操作,这增加了操作的复杂度。此外,与红黑树等其他自平衡二叉搜索树相比,AVL树在插入和删除操作时可能需要更多的旋转操作来保持平衡。因此,在某些特定场景下,红黑树可能比AVL树更受欢迎。然而,在需要高度平衡的场合,AVL树仍然是一个非常好的选择。

二、AVL树的实现

1、基本框架

树节点:


//树节点
template<class K, class V>
struct AVLTreeNode
{
	//构造函数
	AVLTreeNode(const pair<K, V>& val = pair<K, V>())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _val(val)
		, _bf(0)
	{}

	AVLTreeNode<K,V>* _left;	//左孩子指针
	AVLTreeNode<K, V>* _right;	//右孩子指针
	AVLTreeNode<K, V>* _parent;	//父亲指针
	pair<K, V> _val;	//数据
	int _bf;   //节点的平衡因子 -> 右子树高度 - 左子树高度
};

AVL树类:

template<class K, class V>
class AVLTree
{
	//树节点
	typedef AVLTreeNode<K, V> Node;
	private:
	//根节点
	Node* _root = nullptr;
};

2、查找

AVL树的查找操作与普通的二叉搜索树(BST)的查找操作非常相似。由于AVL树本身就是一种二叉搜索树,所以它可以利用二叉搜索树的性质来高效地查找元素。在AVL树中查找元素的过程基本上不涉及树的平衡操作(如旋转),只是简单地利用节点的值和树的结构来定位目标元素。

步骤:从根节点(cur)开始,通过判断key与当前节点key的大小来决定是去左子树还是右子树找该值,循环迭代(cur)上述过程直到找到相同的key后返回该节点,否则就返回空。

//查找
Node* Find(const K& key)
{
	//从根节点开始
	Node* cur = _root;
	while (cur)
	{
		//大了就去左子树中搜索
		if (cur->_val.first > key)
		{
			cur = cur->_left;
		}
		//小了就去右子树中搜索
		else if (cur->_val.first < key)
		{
			cur = cur->_right;
		}
		else
		{
			//找到返回当前节点
			return cur;
		}
	}
	return nullptr;
}

在这里插入图片描述

3、插入

AVL树的插入操作在二叉搜索树(BST)的插入基础上增加了维护树平衡的步骤。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。

在插入后需要更新平衡因子,插入左子树节点时,父节点的平衡因子-1,插入右子树节点时,父节点的平衡因子+1,当更新后的父节点的平衡因子为0就不需要向上更新了(说明插入之前是-1或者1,插入节点后不会影响到上面的节点的平衡因子),当平衡因子(右子树高度 - 左子树高度)为1或者-1时需要向上更新平衡因子(说明插入之前为0,插入后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后的高度就平衡了不需要向上更新。

在这里插入图片描述

(1)插入一个元素使左子树高度大于右子树且失去平衡(单纯一边高(L和parent平衡因子同号)) ------- 右旋转
在这里插入图片描述
平衡因子:根据图分析,旋转后parent和L都为0

// 右单旋
void RotateR(Node* parent)
{
	//左节点
	Node* L = parent->_left;
	//左子树右边第一个节点
	Node* Lr = L->_right;
	//parent的父亲
	Node* pparent = parent->_parent;

	//连接过程
	L->_right = parent;
	parent->_parent = L;

	//该节点可能为空
	if (Lr)
	{
		Lr->_parent = parent;
	}
	parent->_left = Lr;

	//更新L的父节点
	L->_parent = pparent;

	//是根的情况
	if (pparent == nullptr)
	{
		_root = L;
	}
	else
	{
		if (parent == pparent->_left) pparent->_left = L;
		else pparent->_right = L;
	}

	//更新后平衡因子都为0
	parent->_bf = L->_bf = 0;
}

(2)插入一个元素使右子树高度大于左子树且失去平衡(单纯一边高(R和parent平衡因子同号)) ------- 左旋转
在这里插入图片描述

平衡因子:根据图分析,旋转后parent和R都为0

//左旋转
void RotateL(Node* parent)
{
	//右边第一个节点
	Node* R = parent->_right;
	//右子树第一个左节点
	Node* Rl = R->_left;
	//父节点
	Node* pparent = parent->_parent;

	//连接过程

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

	R->_left = parent;
	//更新parent的父节点
	parent->_parent = R;
	//更新R的父节点
	R->_parent = pparent;
	//是根的情况
	if (nullptr == pparent)
	{
		_root = R;
	}
	else
	{
		if (pparent->_left == parent) pparent->_left = R;
		else pparent->_right = R;
	}


	//更新平衡因子
	parent->_bf = R->_bf = 0;
}

(3)插入一个元素使左子树高度大于右子树且失去平衡(不单纯一边高(L和parent平衡因子异号)) ------- 左右旋转
在这里插入图片描述

平衡因子:根据图分析(上面只有Lr = 1 的情况,Lr = 0,或者Lr = -1的情况也按上图的方式推导),更新前Lr的平衡因子为1时更新后L的平衡因子为-1、parent和Lr为0,更新前Lr的平衡因子为-1时更新后parent平衡因子为1、L和Lr平衡因子为0,更新前Lr的平衡因子为0时,L、Lr、parent平衡因子都为0。

// 左右双旋
void RotateLR(Node* parent)
{
	Node* L = parent->_left;
	Node* Lr = L->_right;
	//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变
	int bf = Lr->_bf;
	//左右双旋
	RotateL(L);
	RotateR(parent);

	//更新平衡因子
	if (bf == -1)
	{
		parent->_bf = 1;
		L->_bf = 0;
		Lr->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		L->_bf = -1;
		Lr->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		L->_bf = 0;
		Lr->_bf = 0;
	}
	else
		assert(false);
}

(4)插入一个元素使右子树高度大于左子树且失去平衡(不单纯一边高(R和parent平衡因子异号)) ------- 右左旋转
在这里插入图片描述
平衡因子:根据图分析(上面只有Rl = 1 的情况,Rl = 0,或者Rl = -1的情况也按上图的方式推导),更新前Rl的平衡因子为-1时更新后R的平衡因子为-1、parent和Rl为0,更新前Rl的平衡因子为1时更新后parent平衡因子为-1、R和Rl平衡因子为0,更新前Rl的平衡因子为0时,R、Rl、parent平衡因子都为0。

// 右左双旋
void RotateRL(Node* parent)
{
	Node* R = parent->_right;
	Node* Rl = R->_left;
	//先保存平衡因子
	int bf = Rl->_bf;

	//右左双旋
	RotateR(R);
	RotateL(parent);

	//更新平衡因子
	if (bf == 1)
	{
		parent->_bf = -1;
		R->_bf = 0;
		Rl->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		R->_bf = 1;
		Rl->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		R->_bf = 0;
		Rl->_bf = 0;
	}
	else
		assert(false);
}

(5)根据插入节点(参考搜索二叉树)+更新平衡因子完成插入

//插入
bool Insert(const pair<K, V>& val)
{	
	//找到放val的位置
	Node* cur = _root;
	//作为前驱指针,与val节点连接
	Node* precursor = nullptr;
	while (cur)
	{
		//向左
		if (cur->_val.first > val.first)
		{
			precursor = cur;
			cur = cur->_left;
		}
		//向右
		else if (cur->_val.first < val.first)
		{
			precursor = cur;
			cur = cur->_right;
		}
		//存在相同的值
		else
		{
			return false;
		}
	}
	
	//插入新节点
	cur = new Node(val);
	//不存在根节点,作为根节点
	if (precursor == nullptr) _root = cur;
	//连接在前驱指针左侧
	else if (precursor->_val.first > val.first)
	{
		cur->_parent = precursor;
		precursor->_left = cur;
	}
	//连接在前驱指针右侧
	else
	{
		cur->_parent = precursor;
		precursor->_right = cur;
	}
	
	//更新平衡因子
	while (precursor)
	{

		if (precursor->_left == cur)
			precursor->_bf--;
		else
			precursor->_bf++;

		//为0说明平衡了,不需要再更新了
		if (precursor->_bf == 0)
			break;
		//出现异常需要更新平衡因子,更新完就可以
		else if (precursor->_bf == 2 || precursor->_bf == -2)
		{
			if (precursor->_bf == 2 && cur->_bf == 1)
			{
				RotateL(precursor);
			}
			else if (precursor->_bf == -2 && cur->_bf == -1)
			{
				RotateR(precursor);
			}
			else if (precursor->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(precursor);
			}
			else if (precursor->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(precursor);
			}
			else
			{
				assert(false);
			}
			//更新完平衡了不需要向上更新
			break;
		}

		//进行迭代(向上更新)
		cur = precursor;
		precursor = precursor->_parent;
	}

	return true;
}

4、删除

AVL树删除操作是一个复杂但关键的过程,因为它需要在删除节点后重新调整树的结构以保持其平衡性。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。

在删除后需要更新平衡因子,删除左子树节点时,父节点的平衡因子+1,删除右子树节点时,父节点的平衡因子-1,当更新后的父节点的平衡因子为-1 或者 1就不需要向上更新了(说明删除之前0,删除节点后不会影响到上面的节点的平衡因子),当平衡因子0时需要向上更新平衡因子(说明删除之前为1或者-1,删除后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后当前父节点还是0的话还要继续向上更新,直到更新为-1或者1时就结束更新。

因为复用插入的旋转操作,所以在删除元素后有一些平衡因子在旋转过程中没有正确更新,此时我们就要在旋转完后再次更新。
(1)删除后,右边高了,当R = 0,或者 R = 1 时进行 — 左单旋
在这里插入图片描述
平衡因子:当R的平衡因子为0时,parent的平衡因子需要修改为1,R的平衡因子修改为-1,当R的平衡因子为1时(也是按上图方式推导),parent、R平衡因子都为0。

(2)删除后,左边高了,当L = 0,或者 L = -1 时进行 — 右单旋
在这里插入图片描述
平衡因子:当L的平衡因子为0时,parent的平衡因子需要修改为-1,L的平衡因子修改为-1,当L的平衡因子为-1时(也是按上图方式推导),parent、L平衡因子都为0。

(3)删除后,右边高了,并且出现异号 — 右左双旋

在这里插入图片描述

平衡因子:与插入时使用的右左双旋一样。

(4)删除后,左边高了,并且出现异号 — 左右双旋

在这里插入图片描述
平衡因子:与插入时使用的左右双旋一样。

(5)使用删除操作(参考搜索二叉树的删除)+旋转完成删除

//删除
bool Erase(const K& key)
{
	//从根节点开始搜索
	Node* cur = _root;
	//作为cur的前驱指针
	Node* precursor = nullptr;
	
	//搜索查找
	while (cur)
	{
		if (cur->_val.first > key)
		{
			precursor = cur;
			cur = cur->_left;
		}
			
		else if (cur->_val.first < key)
		{
			precursor = cur;
			cur = cur->_right;
		}
			
		else
			break;
	}

	//找不到
	if (cur == nullptr) return false;

	//假设cur左右节点都存在,找右边最小值替换
	if (cur->_left != nullptr && cur->_right != nullptr)
	{
		Node* tmp1 = cur->_right;
		Node* tmp2 = nullptr;
	
		while (tmp1->_left)
		{
			tmp2 = tmp1;
			tmp1 = tmp1->_left;
		}

		cur->_val = tmp1->_val;
		//tmp1左边没有节点,自己就是最小的节点
		if (tmp2 == nullptr)
		{
			precursor = cur;
			cur = tmp1;	
		}
		else
		{
			cur = tmp1;
			precursor = tmp2;
		}
	}
		
	//假设左边为空和左右节点都为空
	int sign = 0;//左边为-1,右边为1
	Node* deletecur = cur;
	if (cur->_left == nullptr)
	{
		左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)
		if (precursor == nullptr)
		{
			_root = cur->_right;
			delete deletecur;
			return true;
		}
		else
		{
			if (precursor->_left == cur)
			{
				precursor->_left = cur->_right;
				if (cur->_right == nullptr) sign = -1;
			}
				
			else
			{
				precursor->_right = cur->_right;
				if (cur->_right == nullptr) sign = 1;
			}
				
		}
		cur = cur->_right;
	}
	//假设右边为空
	else
	{
		//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)
		if (precursor == nullptr)
		{
			_root = cur->_left;
			delete deletecur;
			return true;
		}
		else
		{
			if (precursor->_left == cur)
				precursor->_left = cur->_left;
			else
				precursor->_right = cur->_left;
		}
		cur = cur->_left;
	}

	//更新平衡因子
	while (precursor)
	{
		//cur出现空的情况
		if (cur == nullptr)
		{
			if (sign == -1)
				precursor->_bf++;
			else
				precursor->_bf--;
		}
		else if (precursor->_left == cur)
			precursor->_bf++;
		else
			precursor->_bf--;

		if (precursor->_bf == -1 || precursor->_bf == 1)
			break;
		else if (precursor->_bf == -2 || precursor->_bf == 2)
		{
			//右边高了左单旋
			if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1))
			{
				//R会做为新的precursor先保存
				Node* R = precursor->_right;
				int bf = precursor->_right->_bf;
				RotateL(precursor);

				//在旋转后的平衡因子不符合预期,需要更新
				if (bf == 0)
				{
					precursor->_bf = 1;
					R->_bf = -1;
				}

				//更新
				precursor = R;
			}
			//左边高了右单旋
			else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1))
			{
				//L会做为新的precursor先保存
				Node* L = precursor->_left;
				int bf = L->_bf;
				RotateR(precursor);

				//在旋转后的平衡因子不符合预期,需要更新
				if (bf == 0)
				{
					precursor->_bf = -1;
					L->_bf = 1;
				}

				//更新
				precursor = L;
			}

			else if (precursor->_bf == 2 && precursor->_right->_bf == -1)
			{
				//L会做为新的precursor先保存
				Node* R = precursor->_right;
				Node* L = R->_left;
				RotateRL(precursor);

				//更新
				precursor = L;
			}
			else if (precursor->_bf == -2 && precursor->_left->_bf== 1)
			{
				//R会做为新的precursor先保存
				Node* L = precursor->_left;
				Node* R = L->_right;
				RotateLR(precursor);

				//更新
				precursor = R;
			}
			else
				assert(false);

			//旋转完precursor更新了再次判断是否平衡了
			if (precursor->_bf == -1 || precursor->_bf == 1) break;
		}
		

		//进行迭代
		cur = precursor;
		precursor = precursor->_parent;

	}

	delete deletecur;
	return true;
}

5、测试

通过两颗子树的高度差是否大于1和判断高度差是否与这颗根节点平衡因子相等来判断是否为AVL树。

//判断是否为平衡树
bool IsBalanceTree()
{
	return _IsBalanceTree(_root);
}
//验证是否是平衡树
bool _IsBalanceTree(Node* root)
{
	if (root == nullptr) return true;

	int l = _Height(root->_left);
	int r = _Height(root->_right);
	int buff = r - l;
	if (root->_bf != buff || buff > 1 || buff < -1)
		return false;

	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
//求树的高度
int _Height(Node* root)
{
	if (root == nullptr) return 0;

	int l = _Height(root->_left);
	int r = _Height(root->_right);

	return max(l, r) + 1;
}

void test()
{
	AVLTree<int, int> a;
	vector<int> arr;
	
	for (int i = 0; i < 10000; i++)
	{
		int m = rand() + i;
		arr.push_back(m);
	}
	//插入10000个随机数
	for (auto e :arr)
	{
		a.Insert({ e,e });
		if (a.IsBalanceTree())
		cout << "是AVL树" << endl;
		else
			assert(false);
	}
	
	//再全部删除
	for (auto e : arr)
	{
		a.Erase(e);
		if (a.IsBalanceTree())
			cout << "是AVL树" << endl;
		else
			assert(false);
	}
}

在这里插入图片描述
在插入和删除过程中没有报错,说明该树是AVL树。

6、总代码

#pragma once
#include<iostream>
#include<string>
#include<cassert>
#include<queue>
#include<vector>

using namespace std;

//树节点
template<class K, class V>
struct AVLTreeNode
{
	//构造函数
	AVLTreeNode(const pair<K, V>& val = pair<K, V>())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _val(val)
		, _bf(0)
	{}

	AVLTreeNode<K,V>* _left;	//左孩子指针
	AVLTreeNode<K, V>* _right;	//右孩子指针
	AVLTreeNode<K, V>* _parent;	//父亲指针
	pair<K, V> _val;	//数据
	int _bf;   //节点的平衡因子 -> 右子树高度 - 左子树高度
};

template<class K, class V>
class AVLTree
{
	//树节点
	typedef AVLTreeNode<K, V> Node;
public:

	~AVLTree()
	{
		DeleteTree(_root);
	}

	//插入
	bool Insert(const pair<K, V>& val)
	{	
		//找到放val的位置
		Node* cur = _root;
		//作为前驱指针,与val节点连接
		Node* precursor = nullptr;
		while (cur)
		{
			//向左
			if (cur->_val.first > val.first)
			{
				precursor = cur;
				cur = cur->_left;
			}
			//向右
			else if (cur->_val.first < val.first)
			{
				precursor = cur;
				cur = cur->_right;
			}
			//存在相同的值
			else
			{
				return false;
			}
		}
		
		//插入新节点
		cur = new Node(val);
		//不存在根节点,作为根节点
		if (precursor == nullptr) _root = cur;
		//连接在前驱指针左侧
		else if (precursor->_val.first > val.first)
		{
			cur->_parent = precursor;
			precursor->_left = cur;
		}
		//连接在前驱指针右侧
		else
		{
			cur->_parent = precursor;
			precursor->_right = cur;
		}
		
		//更新平衡因子
		while (precursor)
		{

			if (precursor->_left == cur)
				precursor->_bf--;
			else
				precursor->_bf++;

			//为0说明平衡了,不需要再更新了
			if (precursor->_bf == 0)
				break;
			//出现异常需要更新平衡因子,更新完就可以
			else if (precursor->_bf == 2 || precursor->_bf == -2)
			{
				if (precursor->_bf == 2 && cur->_bf == 1)
				{
					RotateL(precursor);
				}
				else if (precursor->_bf == -2 && cur->_bf == -1)
				{
					RotateR(precursor);
				}
				else if (precursor->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(precursor);
				}
				else if (precursor->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(precursor);
				}
				else
				{
					assert(false);
				}
				//更新完平衡了不需要向上更新
				break;
			}

			//进行迭代(向上更新)
			cur = precursor;
			precursor = precursor->_parent;
		}

		return true;
	}

	//查找
	Node* Find(const K& key)
	{
		//从根节点开始
		Node* cur = _root;
		while (cur)
		{
			//大了就去左子树中搜索
			if (cur->_val.first > key)
			{
				cur = cur->_left;
			}
			//小了就去右子树中搜索
			else if (cur->_val.first < key)
			{
				cur = cur->_right;
			}
			else
			{
				//找到返回当前节点
				return cur;
			}
		}
		return nullptr;
	}

	//删除
	bool Erase(const K& key)
	{
		//从根节点开始搜索
		Node* cur = _root;
		//作为cur的前驱指针
		Node* precursor = nullptr;
		
		//搜索查找
		while (cur)
		{
			if (cur->_val.first > key)
			{
				precursor = cur;
				cur = cur->_left;
			}
				
			else if (cur->_val.first < key)
			{
				precursor = cur;
				cur = cur->_right;
			}
				
			else
				break;
		}

		//找不到
		if (cur == nullptr) return false;

		//假设cur左右节点都存在,找右边最小值替换
		if (cur->_left != nullptr && cur->_right != nullptr)
		{
			Node* tmp1 = cur->_right;
			Node* tmp2 = nullptr;
		
			while (tmp1->_left)
			{
				tmp2 = tmp1;
				tmp1 = tmp1->_left;
			}

			cur->_val = tmp1->_val;
			//tmp1左边没有节点,自己就是最小的节点
			if (tmp2 == nullptr)
			{
				precursor = cur;
				cur = tmp1;	
			}
			else
			{
				cur = tmp1;
				precursor = tmp2;
			}
		}
			
		//假设左边为空和左右节点都为空
		int sign = 0;//左边为-1,右边为1
		Node* deletecur = cur;
		if (cur->_left == nullptr)
		{
			左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)
			if (precursor == nullptr)
			{
				_root = cur->_right;
				delete deletecur;
				return true;
			}
			else
			{
				if (precursor->_left == cur)
				{
					precursor->_left = cur->_right;
					if (cur->_right == nullptr) sign = -1;
				}
					
				else
				{
					precursor->_right = cur->_right;
					if (cur->_right == nullptr) sign = 1;
				}
					
			}
			cur = cur->_right;
		}
		//假设右边为空
		else
		{
			//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)
			if (precursor == nullptr)
			{
				_root = cur->_left;
				delete deletecur;
				return true;
			}
			else
			{
				if (precursor->_left == cur)
					precursor->_left = cur->_left;
				else
					precursor->_right = cur->_left;
			}
			cur = cur->_left;
		}

		//更新平衡因子
		while (precursor)
		{
			//cur出现空的情况
			if (cur == nullptr)
			{
				if (sign == -1)
					precursor->_bf++;
				else
					precursor->_bf--;
			}
			else if (precursor->_left == cur)
				precursor->_bf++;
			else
				precursor->_bf--;

			if (precursor->_bf == -1 || precursor->_bf == 1)
				break;
			else if (precursor->_bf == -2 || precursor->_bf == 2)
			{
				//右边高了左单旋
				if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1))
				{
					//R会做为新的precursor先保存
					Node* R = precursor->_right;
					int bf = precursor->_right->_bf;
					RotateL(precursor);

					//在旋转后的平衡因子不符合预期,需要更新
					if (bf == 0)
					{
						precursor->_bf = 1;
						R->_bf = -1;
					}

					//更新
					precursor = R;
				}
				//左边高了右单旋
				else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1))
				{
					//L会做为新的precursor先保存
					Node* L = precursor->_left;
					int bf = L->_bf;
					RotateR(precursor);

					//在旋转后的平衡因子不符合预期,需要更新
					if (bf == 0)
					{
						precursor->_bf = -1;
						L->_bf = 1;
					}

					//更新
					precursor = L;
				}

				else if (precursor->_bf == 2 && precursor->_right->_bf == -1)
				{
					//L会做为新的precursor先保存
					Node* R = precursor->_right;
					Node* L = R->_left;
					RotateRL(precursor);

					//更新
					precursor = L;
				}
				else if (precursor->_bf == -2 && precursor->_left->_bf== 1)
				{
					//R会做为新的precursor先保存
					Node* L = precursor->_left;
					Node* R = L->_right;
					RotateLR(precursor);

					//更新
					precursor = R;
				}
				else
					assert(false);

				//旋转完precursor更新了再次判断是否平衡了
				if (precursor->_bf == -1 || precursor->_bf == 1) break;
			}
			

			//进行迭代
			cur = precursor;
			precursor = precursor->_parent;

		}

		delete deletecur;
		return true;
	}

	//判断是否为平衡树
	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}
	
private:

	// 右单旋
	void RotateR(Node* parent)
	{
		//左节点
		Node* L = parent->_left;
		//左子树右边第一个节点
		Node* Lr = L->_right;
		//parent的父亲
		Node* pparent = parent->_parent;

		//连接过程
		L->_right = parent;
		parent->_parent = L;

		//该节点可能为空
		if (Lr)
		{
			Lr->_parent = parent;
		}
		parent->_left = Lr;

		//更新L的父节点
		L->_parent = pparent;

		//是根的情况
		if (pparent == nullptr)
		{
			_root = L;
		}
		else
		{
			if (parent == pparent->_left) pparent->_left = L;
			else pparent->_right = L;
		}

		//更新后平衡因子都为0
		parent->_bf = L->_bf = 0;
	}

	//左旋转
	void RotateL(Node* parent)
	{
		//右边第一个节点
		Node* R = parent->_right;
		//右子树第一个左节点
		Node* Rl = R->_left;
		//父节点
		Node* pparent = parent->_parent;

		//连接过程

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

		R->_left = parent;
		//更新parent的父节点
		parent->_parent = R;
		//更新R的父节点
		R->_parent = pparent;
		//是根的情况
		if (nullptr == pparent)
		{
			_root = R;
		}
		else
		{
			if (pparent->_left == parent) pparent->_left = R;
			else pparent->_right = R;
		}


		//更新平衡因子
		parent->_bf = R->_bf = 0;
	}

	// 右左双旋
	void RotateRL(Node* parent)
	{
		Node* R = parent->_right;
		Node* Rl = R->_left;
		//先保存平衡因子
		int bf = Rl->_bf;

		//右左双旋
		RotateR(R);
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)
		{
			parent->_bf = -1;
			R->_bf = 0;
			Rl->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			R->_bf = 1;
			Rl->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			R->_bf = 0;
			Rl->_bf = 0;
		}
		else
			assert(false);
	}

	// 左右双旋
	void RotateLR(Node* parent)
	{
		Node* L = parent->_left;
		Node* Lr = L->_right;
		//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变
		int bf = Lr->_bf;
		//左右双旋
		RotateL(L);
		RotateR(parent);

		//更新平衡因子
		if (bf == -1)
		{
			parent->_bf = 1;
			L->_bf = 0;
			Lr->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			L->_bf = -1;
			Lr->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			L->_bf = 0;
			Lr->_bf = 0;
		}
		else
			assert(false);
	}
	
	//求树的高度
	int _Height(Node* root)
	{
		if (root == nullptr) return 0;

		int l = _Height(root->_left);
		int r = _Height(root->_right);

		return max(l, r) + 1;
	}
	
	//验证是否是平衡树
	bool _IsBalanceTree(Node* root)
	{
		if (root == nullptr) return true;

		int l = _Height(root->_left);
		int r = _Height(root->_right);
		int buff = r - l;
		if (root->_bf != buff || buff > 1 || buff < -1)
			return false;

		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}

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

		DeleteTree(root->_left);
		DeleteTree(root->_right);

		delete root;
	}

	//根节点
	Node* _root = nullptr;
};

三、AVL树的性能

1、搜索性能分析
时间复杂度:O(log n)

(1) AVL树作为二叉搜索树的一种,其搜索操作与普通的二叉搜索树相同。从根节点开始,根据要搜索的值与节点值的大小关系,决定是向左子树搜索还是向右子树搜索,直到找到目标节点或搜索到叶子节点为止。
(2)由于AVL树的高度被限制在O(log n)以内(其中n是树中节点的数量),因此搜索操作的时间复杂度也是O(log n)。

2、插入性能分析
时间复杂度:O(log n)

(1)插入操作首先执行普通的二叉搜索树插入操作,找到新节点应该插入的位置。 插入新节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(2)如果某个节点的平衡因子变为2或-2(即左右子树高度差超过1),则需要进行旋转操作来恢复平衡。旋转操作包括单旋转(左单旋、右单旋)和双旋转(左右双旋、右左双旋)。
(3)由于旋转操作只在从插入节点到根节点的路径上进行,且路径长度不超过树的高度,因此插入操作的时间复杂度也是O(log n)。

3、删除性能分析
时间复杂度:O(log n)

(1)删除操作首先找到要删除的节点。
(2)如果要删除的节点有两个子节点,则通常使用其右子树中的最小节点(或左子树中的最大节点)来替换它,并删除那个最小(或最大)节点。
(3)删除节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(4)如果某个节点的平衡因子变为2或-2,则需要进行旋转操作来恢复平衡。旋转操作的类型与插入操作类似,包括单旋转和双旋转。
(5)同样地,由于旋转操作只在从删除节点到根节点的路径上进行,且路径长度不超过树的高度,因此删除操作的时间复杂度也是O(log n)。

4、总结

AVL树通过保持树的平衡性,使得搜索、插入和删除操作的时间复杂度都能保持在O(log
n)的水平。这种高效的性能使得AVL树在需要频繁进行动态修改操作的数据结构中非常有用,如数据库索引、文件系统等。然而,需要注意的是,AVL树在每次插入或删除操作后都需要进行平衡调整,这可能会增加一些额外的开销。但在大多数情况下,这种开销是可以接受的,因为AVL树提供了比未平衡的二叉搜索树更好的性能保证。

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

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

相关文章

广东工程职业技术学院财经学院领导一行莅临泰迪智能科技参观交流

7月19日&#xff0c;广东工程职业技术学院财经学院市场调查与统计分析专业主任苏志鹏、专业老师余乐莅临广东泰迪智能科技股份有限公司产教融合实训基地参观交流。泰迪智能科技董事长张良均、副总经理施兴、高校业务部经理孙学镂、校企合作经理吴桂锋进行接待。 仪式伊始&#…

保持形态真实性的大脑生成建模| 文献速递-基于人工智能(AI base)的医学影像研究与疾病诊断

Title 题目 Realistic morphology-preserving generative modelling of the brain 保持形态真实性的大脑生成建模 01 文献速递介绍 医学影像研究通常受到数据稀缺和可用性的限制。治理、隐私问题和获取成本都限制了医学影像数据的访问&#xff0c;加上深度学习算法对数据的…

苍穹外卖(一)之环境搭建篇

Ngnix启动一闪而退 启动之前需要确保ngnix.exe的目录中没有中文字体&#xff0c;在conf目录下的nginx.conf文件查看ngnix的端口号&#xff0c;一般默认为80&#xff0c;若80端口被占用就会出现闪退现象。我们可以通过logs/error.log查看错误信息&#xff0c;错误信息如下&…

k8s+containerd(kvm版)

k8s&#xff08;Kubernetes&#xff09;是由Gogle开源的容器编排引擎&#xff0c;可以用来管理容器化的应用程序和服务&#xff0c;k 高可用&#xff1a;系统在长时间内持续正常地运行&#xff0c;并不会因为某一个组件或者服务的故障而导致整个系统不可用可扩展性&#xff1a…

freesql简单使用操作mysql数据库

参考&#xff1a;freesql中文官网指南 | FreeSql 官方文档 这两天准备做一个测试程序&#xff0c;往一个系统的数据表插入一批模拟设备数据&#xff0c;然后还要模拟设备终端发送数据包&#xff0c;看看系统的承压能力。 因为系统使用的第三方框架中用到了freesql&#xff0c…

【数据结构】包装类、初识泛型

&#x1f387;&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳&#xff0c;欢迎大佬指点&#xff01; 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友…

在windows上使用Docker部署一个简易的web程序

使用Docker部署一个python的web服务&#x1f680; 由于是从事算法相关工作&#xff0c;之前在项目中&#xff0c;需要将写完的代码服务&#xff0c;部署在docker上&#xff0c;以此是开始接触了Docker这个工具&#xff0c;由于之前也没系统学习过&#xff0c;之后应该可能还会用…

[240724] Meta 发布全新大语言模型 Llama 3.1 | Apple 开源全新 AI 模型,打造高效 AI 生态

目录 Meta 发布全新发语言模型 Llama 3.1Apple 开源全新 AI 模型&#xff0c;挑战 Meta&#xff0c;打造高效 AI 生态 Meta 发布全新发语言模型 Llama 3.1 Llama 3.1 提供 8B、70B 和 405B 三种参数规模&#xff0c;其中 405B 版本在通用知识、可控性、数学、工具使用和多语 言…

Python鲁汶意外莱顿复杂图拓扑分解算法

&#x1f3af;要点 &#x1f3af;算法池化和最佳分区搜索&#xff1a;&#x1f58a;网格搜索 | &#x1f58a;发现算法池 | &#x1f58a;返回指定图的最佳划分 | &#x1f58a;返回指定图的最佳分区 | &#x1f3af;适应度和聚类比较功能&#xff1a;&#x1f58a;图的划分 |…

django电商用户消费数据分析系统-计算机毕业设计源码20891

摘 要 随着电子商务的快速发展&#xff0c;电商平台积累了大量的用户消费数据。为了更好地理解用户行为、优化商品结构和提升用户体验&#xff0c;本文设计并实现了一个基于Django框架的电商用户消费数据分析系统。 该系统包含后台首页、系统用户&#xff08;管理员&#xf…

探索 GPT-4o mini:成本效益与创新的双重驱动

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

elk日志索引被锁blocks,日志无法写入

现象&#xff1a; kafka积压&#xff0c;logstash无法将日志写入到es logstash报错&#xff1a; [logstash.outputs.elasticsearch][main][] Retrying failed action {:status>403 :error>{“type”>“cluster_block_exception”, “reason”>“index [] blocked …

“微软蓝屏”全球宕机,敲响基础软件自主可控警钟

上周五&#xff0c;“微软蓝屏”“感谢微软 喜提假期”等词条冲上热搜&#xff0c;全球百万打工人受此影响&#xff0c;共同见证这一历史性事件。据微软方面发布消息称&#xff0c;旗下Microsoft 365系列服务出现访问中断。随后在全球范围内&#xff0c;包括企业、政府、个人在…

Spring Boot集成starrocks快速入门Demo

1.什么是starrocks&#xff1f; StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;就可以用 StarRocks 来支持多种数据分析场景的极速分析。 Star…

矩阵分析——线性积分方程组的矩阵解法研究

矩阵分析——线性积分方程组的矩阵解法研究 前言线性积分方程组的矩阵解法研究 前言 “矩阵分析”是一门选修课&#xff0c;当时选这门课程的原因是想着图像处理就涉及到很多矩阵运算。但没想到的是这门课程吧虽然是选修&#xff0c;最后的结课要求是让我们写一篇“论文”&…

第19讲EtherNet/IP网络基础

EtherNet/IP网络知识 一、EtherNet/IP概述 二、EtherNet/IP网络的定位 1、最上层-信息层:主要进行上位机网络信号交互或者控制层信号的传递。 比较常见的话是工控机或者说PLC,就像大脑对信息进行发送的这样一个控制。 EtherNet/IP网络属于最高层——信息层,主要负责信号的…

vue3中父子组件通讯

在子组件HelloWorld.vue中&#xff1a; <template><div class"hello">111111</div></template><script lang"ts"> import { Options, Vue } from vue-class-component;Options({props: {msg: String} }) export default cl…

多路复用IO、TCP并发模型

时分复用 CPU单核在同一时刻只能做一件事情&#xff0c;一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片&#xff0c;不同事件流的时间片交替进行)。在计算机系统中&#xff0c;我们用线程或者进程来表示一条执行流&#xff0c;通过不同的线程或进程在操作系…

群辉NAS利用AList搭建混合云盘①套件安装及百度云盘挂载

目录 一、群辉NAS准备 二、远程访问 三、安装套件 四、挂载公有云盘 1、挂载百度网盘 ……(未完待续) 公有云盘是由云服务提供商运营,向广大用户提供数据存储和文件共享服务的一种在线存储解决方案。 其优点包括: 1. 方便易用:用户可以通过互联网随时随地访问自己存…

Java企业微信服务商代开发获取AccessToken示例

这里主要针对的是企业微信服务商代开发模式 文档地址 可以看到里面大致有三种token&#xff0c;一个是服务商的token&#xff0c;一个是企业授权token&#xff0c;还有一个是应用的token 这里面主要有下面几个参数 首先是服务商的 corpid 和 provider_secret &#xff0c;这个可…