数据结构(五)——AVL树(平衡二叉搜索树)

news2025/5/21 2:50:46

目录

前言

AVL树概念

AVL树的定义

AVL树的插入

右旋转

左旋转

左右双旋

右左双旋

插入代码如下所示

AVL树的查找

AVL树的遍历

AVL树的节点个数以及高度

判断平衡

AVL树代码如下所示

小结


前言

前面我们在数据结构中介绍了二叉搜索树,其中提到了二叉搜索树的缺陷:极端情况下时间复杂度会坍缩成O(N),如何解决它不够稳定这个问题呢?我们发现,只要让二叉树成为一个满二叉树,就可以很好的控制它在查找的过程中的时间复杂度为O(logN),接下来就让我们一起来学习平衡二叉树当中的AVL树吧。

参考文章:

据结构(四)——二叉搜索树的实现(C++版)

AVL树概念

AVL树是一个平衡二叉搜索树,它的发明者是前苏联的两位科学家G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表,因此我们用他们两个的名字的首字母命名。

它具备以下性质:

1.左右子树高度差不超过1,.它的左右子树都是AVL树

2.它的左子树节点上的值小于根节点上的值,它的右子树节点上的值大于根节点上的值,即左子树<根节点<右子树

3.查找的时间复杂度是O(N)

为了方便理解,我们在这里引入平衡因子的概念:平衡因子=右子树高度-左子树高度

引入平衡因子的作用是,我们可以通过控制平衡因子来判平衡,然后对AVL树进行调整

AVL树的定义

#include<iostream>
using namespace std;

#include<assert.h>

template<class K,class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;

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

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

private:
	Node* _root=nullptr;
};

我们可以看到,相比于二叉搜索树,我们对AVL树的定义多了一个父节点的指针,同时引入了平衡因子bf,引入父节点的指针是因为我们需要通过父节点的指针找到上一个节点,然后通过不断向上调整,直到调整到根节点或者使其成为一个AVL树为止。其中也有不引入父节点指针和平衡因子bf的实现方法,比如我们可以将遍历过的节点放在一个栈当中,然后通过栈的性质找到之前的节点(这个方法我们不在这里讲解,后续如果有时间,我会专门写一篇文章来讲解这个实现方法)。

AVL树的插入

根据上面AVL树的性质,我们首先按照二叉搜索树的规则进行插入,然后让插入节点的父节点的指针指向上一个节点,更新父节点的平衡因子,如果平衡因子为0,那么就停止插入;如果平衡因子的绝对值为1,那么继续向上更新;如果平衡因子的绝对值为2,那么就特殊处理。

综上所述,我们可以将AVL树的插入分成以下几个步骤:

第一步:按照二叉搜索树的规则进行插入,链接父节点的指针

第二步:向上更新平衡因子,如果平衡因子为0就结束;如果平衡因子的绝对值为1就继续向上更新;如果平衡因子的绝对值为2就特殊处理

平衡因子更新结束的条件:更新到根节点或者平衡因子为0。

平衡因子的更新规则:

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

2.新增节点在父节点的左子树,父节点的平衡因子减一;新增节点在父节点的右子树,父节点的平衡因子加一

根据上述分析,我们可以写出如下代码:

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
		{
			cout << "插入值已存在,请重新输入" << endl;
			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 (parent->_left == cur)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}

		if (parent->_bf == 0)
		{
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = cur->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//特殊处理

			break;
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

下面我们对特殊处理的情况进行分析。

我们发现,如果平衡因子的绝对值是2的时候,我们必须通过特殊处理才能保证AVL树的结构不会被破坏,那么一共有多少种特殊情况呢?我们一起来分析。

如上图所示,我们大概可以将平衡因子的绝对值为2的情况分为上述四种情况,接下来我将通过上面的四种情况来分析。

右旋转

首先我们来分析情况一:

情况一的情形是一种特殊情况,分析之后我们发现,要解决这个问题只需要让cur节点作为根节点,让parent节点作为cur的右子树,然后更新每个节点的父节点指针即可满足AVL树的性质,如下图所示:

对于情况一,我们可以将其推广到一般情况,如下图所示:

 分析上述图片中的情况,我们将父节点的左节点定义为subL,subL的右节点定义为subLR,调整过后subL成为了新的根节点,parent变成了subL的右节点,subLR成为了parent的左节点。分析平衡因子,更新前parent的平衡因子为-2,subL的平衡因子为-1;更新结束后,subL和parent的平衡因子变成了0。

总结一下,我们发现,这种节点整体偏向左边,需要向右调整节点的情况我们称为右旋转,其构成的条件是:parent平衡因子为-2,且cur的平衡因子为-1,所以我们可以写下如下代码:

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

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

	if (subLR)
		subLR->_parent = parent;
	Node* grendParent = parent->_parent;
	parent->_parent = subL;

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

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

分析上述代码,我们看到我们的更新步骤分下面几步:

第一步:获取subL和subLR节点,然后让parent的左指针指向subLR,让subL的右节点指针指向parent

第二步:调整父节点指针。如果subLR存在则让其父节点指针指向parent;保存grandparent指针,然后让父节点的父节点指针指向subL;

第三步:调整subL节点的根节点指针。如果parent是根节点,那么就让subL成为新的根节点;如果不是根节点,那么让grendparent相应的指针指向subL

第四步:更新subL和parent节点的平衡因子。

左旋转

我们看到情形三,我们将其推广到一般情况,如下图所示:

 分析上述图片中的情况,我们将父节点的右节点定义为subR,subL的左节点定义为subRL,调整过后subR成为了新的根节点,parent变成了subR的左节点,subLR成为了parent的右节点。分析平衡因子,更新前parent的平衡因子为2,subL的平衡因子为1;更新结束后,subL和parent的平衡因子变成了0。

总结一下,我们发现,这种节点整体偏向右边,需要向左调整节点的情况我们称为左旋转,其构成的条件是:parent平衡因子为2,且cur的平衡因子为1,所以我们可以写下如下代码:

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

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

	Node* grendParent = parent->_parent;
	if (subRL)
		subRL->_parent = parent;
	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}

	else
	{
		if (grendParent->_left == parent)
		{
			grendParent->_left = subR;
		}
		else
		{
			grendParent->_right = subR;
		}
		subR->_parent = grendParent;
	}
	parent->_bf = subR->_bf = 0;
}

分析上述代码,我们看到我们的更新步骤分下面几步:

第一步:获取subR和subRL节点,然后让parent的右指针指向subRL,让subL的左节点指针指向parent

第二步:调整父节点指针。如果subRL存在则让其父节点指针指向parent;保存grandparent指针,然后让父节点的父节点指针指向subR;

第三步:调整subL节点的根节点指针。如果parent是根节点,那么就让subR成为新的根节点;如果不是根节点,那么让grendparent相应的指针指向subR

第四步:更新subR和parent节点的平衡因子。

左右双旋

我们看到情形二,我们将其推广到一般情况,如下图所示:

分析上述图片中的情况,我们将parent的左节点定义为subL,将subL的右节点定义为sublr,对于这个情况,我们可以根据subLR平衡因子的值将这个情形分为三种不同的情况,最终调整后的结果我们看到,subLR成为了根节点,subL成为了subLR的左节点,parent成为了subLR的右节点。

仔细观察我们发现,这种情况下的调整可以先以parent的左节点为旋转中心,进行一次左旋转,然后再以parent为旋转中心,进行一次右旋转,因此我们将这种情况称为左右双旋,满足左右双旋的条件是:parent节点的平衡因子是-2,cur节点的平衡因子是1,旋转结束后我们根据subLR节点的平衡因子的值对subLR、subL、parent的平衡因子进行赋值,因此我们可以写下如下代码:

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

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

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

右左双旋

我们看到情形四,我们将其推广到一般情况,如下图所示:

分析上述图片中的情况,我们将parent的右节点定义为subR,将subR的左节点定义为subRL,对于这个情况,我们可以根据subRL平衡因子的值将这个情形分为三种不同的情况,最终调整后的结果我们看到,subRL成为了根节点,subR成为了subRL的左节点,parent成为了subRL的右节点。

仔细观察我们发现,这种情况下的调整可以先以parent的右节点为旋转中心,进行一次右旋转,然后再以parent为旋转中心,进行一次左旋转,因此我们将这种情况称为右左双旋,满足右左双旋的条件是:parent节点的平衡因子是2,cur节点的平衡因子是-1,旋转结束后我们根据subRL节点的平衡因子的值对subRL、subR、parent的平衡因子进行赋值,因此我们可以写下如下代码:

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

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

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

插入代码如下所示

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
		{
			cout << "插入值已存在,请重新输入" << endl;
			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 (parent->_left == cur)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}

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

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

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

	Node* grendParent = parent->_parent;
	if (subRL)
		subRL->_parent = parent;
	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}

	else
	{
		if (grendParent->_left == parent)
		{
			grendParent->_left = subR;
		}
		else
		{
			grendParent->_right = subR;
		}
		subR->_parent = grendParent;
	}
	parent->_bf = subR->_bf = 0;
}

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

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

	if (subLR)
		subLR->_parent = parent;
	Node* grendParent = parent->_parent;
	parent->_parent = subL;

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

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

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

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

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

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

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

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

AVL树的查找

AVL树的查找与二叉搜索树相同,从根节点开始查找,如果目标值大于根节点的值就走向右子树,如果目标值小于根节点的值就走向左子树,其代码如下所示:

Node* Find(const pair<K, V>& kv)
{
	Node* cur = _root;
	while (cur)
	{
		if (kv.first < cur->_kv.first)
		{
			cur = cur->_left;
		}
		else if (kv.first > cur->_kv.first)
		{
			cur = cur->_right;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

AVL树的遍历

我们通过中序递归的方式遍历整个AVL树,其代码如下所示:

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
private:
    void _InOrder(Node* root)
    {
	    if (root == nullptr)
	{
		return;
	}

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

AVL树的节点个数以及高度

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}
	
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

与遍历相同,我们都是通过递归的方式遍历AVL树,不同的是它的高度是通过后序遍历完成的

判断平衡

bool _IsBalanceTree(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}

	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	int diff = rightHeight - leftHeight;

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

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

AVL树代码如下所示

#include<iostream>
using namespace std;

#include<assert.h>

namespace FJY
{
	template<class K,class V>
	struct AVLTreeNode
	{
		pair<K, V> _kv;
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		int _bf;

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

	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 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
				{
					cout << "插入值已存在,请重新输入" << endl;
					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 (parent->_left == cur)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

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

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

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

			Node* grendParent = parent->_parent;
			if (subRL)
				subRL->_parent = parent;
			parent->_parent = subR;
			if (parent == _root)
			{
				_root = subR;
				subR->_parent = nullptr;
			}

			else
			{
				if (grendParent->_left == parent)
				{
					grendParent->_left = subR;
				}
				else
				{
					grendParent->_right = subR;
				}
				subR->_parent = grendParent;
			}
			parent->_bf = subR->_bf = 0;
		}

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

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

			if (subLR)
				subLR->_parent = parent;
			Node* grendParent = parent->_parent;
			parent->_parent = subL;

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

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

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

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

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

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

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

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

		Node* Find(const pair<K, V>& kv)
		{
			Node* cur = _root;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					cur = cur->_left;
				}
				else if (kv.first > cur->_kv.first)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}
		int Height()
		{
			return _Height(_root);
		}

		int Size()
		{
			return _Size(_root);
		}
		
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		int _Height(Node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}
			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);
			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		int _Size(Node* root)
		{
			return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
		}

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

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

		bool _IsBalanceTree(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}

			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);
			int diff = rightHeight - leftHeight;

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

			return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
		}
	private:
		Node* _root=nullptr;
	};
}

小结

本篇博客我们重点介绍了AVL树的插入,以及其他接口的实现,通过了解AVL树的底层实现,我们将更加深入理解平衡二叉搜索树的概念,为之后学习红黑树打下了基础,后续我们将讲解map和set的底层实现,它的底层是用的红黑树完成,所以学习AVL树将为我们后面的学习打下基础

以上就是本篇博客的主要内容,如果对您有所帮助的话,记得点赞、评论、关注加转发,您的支持就是我创作的最大动力

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

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

相关文章

C++类型转换详解

目录 一、内置 转 内置 二、内置 转 自定义 三、自定义 转 内置 四、自定义 转 自定义 五、类型转换规范化 1.static_case 2.reinterpret_cast 3.const_cast 4.dynamic_cast 六、RTTI 一、内置 转 内置 C兼容C语言&#xff0c;在内置类型之间转换规则和C语言一样的&am…

excel数据透视表大纲格式改为表格格式

现有这样一个数据透视表&#xff1a; 想要把他变成这样的表格格式&#xff1a; 操作步骤&#xff1a; 第一步&#xff1a; 效果&#xff1a; 第二步&#xff1a; 效果&#xff1a; 去掉分类汇总&#xff1a; 效果&#xff1a; 去掉展开/折叠按钮&#xff1a; 操作方式&#xf…

天梯集训+代码打卡笔记整理

1.着色问题 直接标注哪些行和列是被标注过的&#xff0c;安全格子的数量就是未标注的行*列 #include <bits/stdc.h> using namespace std;const int N 1e510; int hang[N],lie[N];int main(){int n,m;cin>>n>>m;int q;cin>>q;while(q--){int x,y;ci…

支付系统设计入门:核心账户体系架构

&#x1f449;目录 1 账户记账理论 2 账户设计 3 账户性能问题 4 账户核心架构 5 小结 第三方支付作为中立的第三方&#xff0c;截断了用户和商户的资金流&#xff0c;资金先从用户账户转移到第三方支付平台账户&#xff0c;得到双方确认后再从支付平台账户转移到商户账户。 支…

[LevelDB]Block系统内幕解析-元数据块(Meta Block)元数据索引块(MetaIndex Block)索引块(Index Block)

本文内容组织形式 Block的基本信息作用示意图举例说明 源码解析Footer格式写入&读取编码&解码 元数据块&#xff08;Meta Block&#xff09;构建&读取 元数据索引块构建&读取 索引块定义构建&读取核心方法-FindShortestSeparator&FindShortSuccessor作…

leetcode:905. 按奇偶排序数组(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums&#xff0c;将 nums 中的的所有偶数元素移动到数组的前面&#xff0c;后跟所有奇数元素。 返回满足此条件的 任一数组 作为答案。 示例 1&#xff1a; 输入&#xff1a;nums [3,1,2,4] 输出&#xff1a;[2,4,3,1] 解释&#xff1a…

断言与反射——以golang为例

断言 x.(T) 检查x的动态类型是否是T&#xff0c;其中x必须是接口值。 简单使用 func main() {var x interface{}x 100value1, ok : x.(int)if ok {fmt.Println(value1)}value2, ok : x.(string)if ok {//未打印fmt.Println(value2)} }需要注意如果不接受第二个参数就是OK,这…

【数据结构】排序算法(下篇·开端)·深剖数据难点

前引&#xff1a;前面我们通过层层学习&#xff0c;了解了Hoare大佬的排序精髓&#xff0c;今天我们学习的东西可能稍微有点难度&#xff0c;因此我们必须学会思想&#xff0c;我很受感慨&#xff0c;借此分享一下&#xff1a;【用1520分钟去调试】&#xff0c;如果我们遇到了任…

山东大学软件学院创新项目实训开发日志(9)之测试前后端连接

在正式开始前后端功能开发前&#xff0c;在队友的帮助下&#xff0c;成功完成了前后端测试连接&#xff1a; 首先在后端编写一个测试相应程序&#xff1a; 然后在前端创建vue 并且在index.js中添加一下元素&#xff1a; 然后进行测试&#xff0c;测试成功&#xff1a; 后续可…

蓝桥杯C++组算法知识点整理 · 考前突击(上)【小白适用】

【背景说明】本文的作者是一名算法竞赛小白&#xff0c;在第一次参加蓝桥杯之前希望整理一下自己会了哪些算法&#xff0c;于是有了本文的诞生。分享在这里也希望与众多学子共勉。如果时间允许的话&#xff0c;这一系列会分为上中下三部分和大家见面&#xff0c;祝大家竞赛顺利…

springboot调用python文件,python文件使用其他dat文件,适配windows和linux,以及docker环境的方案

介绍 后台是用springboot技术&#xff0c;其他同事做的算法是python&#xff0c;现在的需求是springboot调用python&#xff0c;python又需要调用其他的数据文件&#xff0c;比如dat文件&#xff0c;这个文件是app通过蓝牙获取智能戒指数据以后&#xff0c;保存到后台&#xf…

GSO-YOLO:基于全局稳定性优化的建筑工地目标检测算法解析

论文地址:https://arxiv.org/pdf/2407.00906 1. 论文概述 《GSO-YOLO: Global Stability Optimization YOLO for Construction Site Detection》提出了一种针对建筑工地复杂场景优化的目标检测模型。通过融合全局优化模块(GOM)​、稳定捕捉模块(SCM)​和创新的AIoU损失函…

系统思考—提升解决动态性复杂问题能力

感谢合作伙伴的信任推荐&#xff01; 客户今年的人才发展重点之一&#xff0c;是提升管理者应对动态性、复杂性问题的能力。 在深入交流后&#xff0c;系统思考作为关键能力模块&#xff0c;最终被纳入轮训项目——这不仅是一次培训合作&#xff0c;更是一场共同认知的跃迁&am…

P1162 洛谷 填涂颜色

题目描述 思考 看数据量 30 而且是个二维的&#xff0c;很像走迷宫 直接深搜&#xff01; 而且这个就是搜连通块 代码 一开始的15分代码&#xff0c;想的很简单&#xff0c;对dfs进行分类&#xff0c;如果是在边界上&#xff0c;就直接递归&#xff0c;不让其赋值&#xff0c…

docker安装nginx,基础命令,目录结构,配置文件结构

Nginx简介 Nginx是一款轻量级的Web服务器(动静分离)/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。其特点是占有内存少&#xff0c;并发能力强. &#x1f517;官网 docker安装Nginx &#x1f433; 一、前提条件 • 已安装 Docker&#xff08;dock…

用Django和AJAX创建一个待办事项应用

用Django和AJAX创建一个待办事项应用 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 用Django和AJAX创建一个待办事项应用让我们创建一个简单的 Django 项目,其中包含不同类型的 A…

JavaScript:游戏开发的利器

在近年来的科技迅速发展中&#xff0c;JavaScript 已逐渐成为游戏开发领域中最受欢迎的编程语言之一。它的跨平台特性、广泛的社区支持、丰富的库和框架使得开发者能够快速、有效地创建各种类型的游戏。本文将深入探讨 JavaScript 在游戏开发中的优势。 一、跨平台支持 JavaSc…

C语言今天开始了学习

好多年没有弄了&#xff0c;还是捡起来弄下吧 用的vscode 建议大家参考这个配置 c语言vscode配置 c语言这个语言简单&#xff0c;但是今天听到了一个消息说python 不知道怎么debug。人才真多啊

电商素材革命:影刀RPA魔法指令3.0驱动批量去水印,实现秒级素材净化

本文 去除水印实操视频展示电商图片水印处理的困境​影刀 RPA 魔法指令 3.0 强势登场​利用魔法指令3.0两步实现去除水印操作关于影刀RPA 去除水印实操视频展示 我们这里选择了4张小红书里面比较帅气的图片&#xff0c;但凡用过小红书的都知道&#xff0c;小红书右下角是会有小…

CVA6:支持 Linux 的 RISC-V CPU CORE-V

RISC-V 是一种开源的可扩展指令集架构 (ISA)&#xff0c;在过去几年中广受欢迎。RISC-V 的主要特性之一是它采用整体架构中性设计&#xff0c;支持浮点运算、加载存储架构、符号扩展加速和多路复用器简化。这使得 RISC-V 成为 FPGA 上软处理器的经济实惠的选择。自 RISC-V ISA …