解读AVL树:平衡二叉搜索树的奥秘

news2024/10/27 23:03:17

✨✨小新课堂开课了,欢迎欢迎~✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++:由浅入深篇

小新的主页:编程版小新-CSDN博客

前言:

前面我们已经介绍了二叉搜索树,今天我们要学习的AVL是一颗高度平衡的二叉搜索树,如何让一棵树保持平衡呢?欲知后事,请看下文。

 一.AVL树的概念

AVL树是最先发明的平衡二叉搜索树,AVL是一颗空树,或者具备下列性质的二叉搜索树:

1.它的左右子树都是AVL树

2.它的左右子树的高度差的绝对值不超过1

AVL树是一颗高度平衡的二叉搜索树,通过控制高度差去控制平衡。

二.平衡因子的概念

 AVL树实现这里我们引入一个平衡因子(balance factor)的概念,每个结点都有一个平衡因子,平衡因子是指一个节点的右子树高度与左子树高度之差,也就是说任何结点的平衡因子等于0/1/-1(左右子树的高度差的绝对值不超过1)

AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像一个风向标一样。

思考一下为什么AVL树是高度平衡二叉搜索树,要求高度差不超过1,而不是高度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。比如一棵树是2个结点,4个结点等情况下,无法做到高度差是0。

平衡因子以右子高度减去左子树高度为例。

AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在logN ,那么增删查改的效率也可以控制在logN ,相比二叉搜索树有了本质的提升。

三.AVL树的实现

3.1AVL树的结构

template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到
	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;
};

3.2AVL树的插入

3.2.1 AVL树插入一个值的大概过程

1. 插入一个值按二叉搜索树规则进行插入。

2. 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子。

更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。

3. 更新平衡因子过程中没有出现问题,则插入结束。

4. 更新平衡因子过程中出现不平衡,对不平衡子树进行旋转。

旋转后将子树调平衡的同时降低了子树的高度,不会再影响上一层,所以插入结束。

总结:

3.2.2平衡因子的更新

更新原则:

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

• 只有子树高度变化才会影响当前结点平衡因子

• 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在parent的左子树,parent平衡因子--

• parent所在子树的高度是否变化决定了是否会继续往上更新

示例一:

示例二:

更新停止条件:

1• 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0 或者 1->0,说明更新前parent子树一边高一边低新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束

2• 更新后parent的平衡因子等于1 或 -1,更新前更新中parent的平衡因子变化为0->1 或者 0->-1,说明更新前parent子树两边一样高,新增的插入结点后,parent所在的子树一边高一边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新

3• 更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因子变化为1->2 或者 -1->-2,说明更新前parent子树一边高一边低新增的插入结点在高的那边,parent所在的子树高的那边更高了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理

旋转的目标有两个:

1、把parent子树旋转平衡。

2、降低parent子树的高度,恢复到插入结点以前的高度。(插入之前是符合AVL树的要求的)

所以旋转后也不需要继续往上更新,插入结束。

3.2.3插入节点及其更新平衡因子

把上面的更新过程理解清楚了,代码的实现就简单了。

//插入
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 (cur->_kv.first < kv.first)
		{
			//往右走
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			//往左走
			parent = cur;
			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)
	{
		//新增节点在parent的左子树
		if (cur == parent->_left)
		{
			parent->_bf--;//平衡因子--
		}
		else
		{
			//新增节点在parent的右子树
			parent->_bf++;//平衡因子++
		}

		//检查是否需要向上更新祖先的平衡因子
		if (parent->_bf == 0)
		{
			//更新结束
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			//新增的插入结点后,parent所在的子树符合平衡要求
			//但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//新增的插入结点在高的那边,parent所在的子树高的那边更高了
			// 破坏了平衡,parent所在的子树不符合平衡要求
			// 需要旋转处理
			

			break;//旋转之后,就更新结束了
		}
		else
		{
			assert(false);
		}

	}

	return true;
}

3.3旋转

3.3.1 旋转的原则

旋转原则:

1. 保持搜索树的规则

2. 让旋转的树从不平衡变平衡,其次降低旋转树的高度

旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

说明:下面的图中,有些结点我们给的是具体值,如10和5等结点,这里是为了方便讲解,实际中是什么值都可以,只要大小关系符合搜索树的规则即可。

3.3.2右单旋

本图1展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整棵树的根,也可能是一个整棵树中局部的子树的根(在实现代码时,这里就是一个需要考虑的小细节)。

这里图一a/b/c是高度为h的子树,是一种概括抽象表示,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体图2/图3/图4/图5进行了详细描述。

其中,parent是失衡节点,其左子树被命名为subL,其左子树的右子树被命名为subLR。

旋转规则如下:

1.将subLR链接到parent的左边。

2.将parent链接到subL的右边

3.将parent与sunL的平衡因子置为0

示例一:

示例二:

示例三:

示例四:

 如果去讨论插入前a/b/c是高度为4的AVL子树的话,情况更是多的数不胜数,不过我们不必纠结这个问题,因为我们已经给出了如图一所示的旋转模版,所有的情况都包含在内,我们只要掌握右旋的旋转规则就能解决问题。

实践:右单旋代码实现

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

	parent->_left = subLR;

	//更改_parent的指向
	if (subLR)//可能高度为0,如情况1
	{
		subLR->_parent = parent;
	}

	//提前记录parent的父亲节点,因为parent也可能是一整棵树中局部子树的根
	Node* Pparent = parent->_parent;

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


	//parent之前可能是整棵树的根,也可能是一整棵树中局部的子树的根
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		//如果parent之前只是一颗树中局部的子树的根
		//我们就需要提前记录parent的_parent
		if (Pparent->_left == parent)
		{
			Pparent->_left = subL;
		}
		else
		{
			Pparent->_right = subL;
		}

		subL->_parent = Pparent;

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

3.3.3左单旋

本图6展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里a/b/c是高度为h的子树,是一种概括抽象表示,他代表了所有左单旋的场景,实际左单旋形态有很多种,具体跟上面右旋类似。

 其中,parent是失衡节点,其右子树被命名为subR,其右子树的左子树被命名为subRL。

旋转规则如下:

1.将subRL链接到parent的右边

2.将parent链接到subR的左边

3.将parent与subR的平衡因子置为0

我们不具体的向右旋那样,具体讨论了,下面我们去看几个例子,熟悉一下左旋的过程。

可以发现旋转之后,即保持了搜索树的规则又让旋转的树从不平衡变平衡,其次降低旋转树的高度。 

 实践:左单旋代码实现

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

		parent->_right = subRL;

		//更新_parent的指向
		if (subRL)//可能高度为0
		{
			subRL->_parent = parent;
		}

		//	//提前记录parent的父亲节点,因为parent之前也可能是一整棵树中局部的子树的根
		Node* Pparent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//parent之前可能是整棵树的根,也可能是一整棵树中局部的子树的根
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			//如果parent之前只是一颗树中局部的子树的根
		   //我们就需要提前记录parent的_parent
			if (Pparent->_left == parent)
			{
				Pparent->_left = subR;
			}
			else
			{
				Pparent->_right = subR;
			}

			subR->_parent = Pparent;

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

		}
	}

3.3.4左右双旋

通过图7和图8可以看到,左边高时,如果插入位置不是在a子树,而是插入在b子树,b子树高度从h变成h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边高,左单旋解决的纯粹的右边高,但是插入在b子树中,10为跟的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高,需要用两次旋转才能解决,以5为旋转点进行一个左单旋,以10为旋转点进行⼀个右单旋,这棵树这棵树就平衡了。

 由图可以更加直观的看到,像这种不是纯粹的一边高的情况,只靠左旋/右旋是不能解决问题的。不仅树不平衡,我们在更新平衡因子的时候,也有错误。

那我们该如何是好呢?

对于这种情况我们需要先左单旋,再右单旋的方式,其中,失衡节点为parent,其左子树节点为subL,而有左子树的右子树为subLR。

其调整规则如下:

1.先对subL进行左旋

2.再对parent 进行右旋

3.调整平衡因子

下面我们具体看个示例。

但是新增节点的位置不同,平衡因子的更新也会有差异,这里我们分为三种情况。

1.当subLR = -1时,如上图所示,调整后subL = 0;subLR = 0;parent = 1

2.当subLR= 0时,如下图所示,调整后subL = 0;subLR = 0;parent = 0

3.当subLR = 1时,如下图所示,调整后subL = -1;subLR = 0;parent = 0。

实践:左右双旋代码实现

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

	int bf = subLR->_bf;

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

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

 3.3.5右左双旋

和左右双旋的情况类似,当出现不是纯粹的某一边高时,仅仅使用一次单旋是解决不了问题的。例如对12而言是左边高,对8而言是右边高。我们就要先对12进行右旋,再对8进行左旋才能解决问题。

对于这种情况我们需要先右单旋,再左单旋的方式,设失衡节点为parent,其右子树节点为subR,而有右子树的左子树为subRL。

其调整规则如下:

先对subR进行右旋

再对parent进行左旋

调整平衡因子

我们针对上面的例子,具体实践看看。

 

但是新增节点的位置不同,平衡因子的更新也会有差异,这里我们分为三种情况。

1.当subRL = -1时,如上图所示,调整后subR = 1;subRL = 0;parent = 0

2.当subRL = 0时,如下图所示,调整后subR = 0;subRL = 0;parent = 0

3.当subRL = 1时,如下图所示,调整后subR = 0;subRL = 0;parent = -1

实践:右左双旋代码实现

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

	int bf = subRL->_bf;

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

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

3.3.6插入完整代码

//插入
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 (cur->_kv.first < kv.first)
		{
			//往右走
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			//往左走
			parent = cur;
			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)
	{
		//新增节点在parent的左子树
		if (cur == parent->_left)
		{
			parent->_bf--;//平衡因子--
		}
		else
		{
			//新增节点在parent的右子树
			parent->_bf++;//平衡因子++
		}

		//检查是否需要向上更新祖先的平衡因子
		if (parent->_bf == 0)
		{
			//更新结束
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			//新增的插入结点后,parent所在的子树符合平衡要求
			//但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//新增的插入结点在高的那边,parent所在的子树高的那边更高了
			// 破坏了平衡,parent所在的子树不符合平衡要求
			// 需要旋转处理
			if (parent->_bf == -2 && cur->_bf ==-1)
			{
				RotateR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(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;
}

3.4AVL树的查找

和二叉搜索树的查找逻辑一样,搜索效率为 O(logN)。

Node* Find(const k& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > key)
		{
			cur = cur->_left;
		}
		else if (cur->_kv.first < key)
		{
			cur=cur->_right
		}
		else
		{
			return cur;
		}
	}

	return nullptr;
}

3.5AVL树平衡检测

我们实现的AVL树是否合格,我们通过检查左右子树高度差的的程序进行反向验证,同时检查一下结点的平衡因子更新是否出现了问题。

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

		int LeftHight = _Hight(root->_left);
		int RightHight = _Hight(root->_right);

		return LeftHight > RightHight ? LeftHight + 1 : RightHight+1;
	}

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

		int LeftHight = _Hight(root->_left);
		int RightHight = _Hight(root->_right);
		int diff = RightHight - LeftHight;

		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "高度差异常" << endl;
			return false;
		}

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

		// root的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);

	}

四.源码

#pragma once
#include<iostream>
#include<assert.h>
#include<utility>

using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到
	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:

	AVLTree()
	{}

	AVLTree(const AVLTree& t)
	{
		_root = copy(t._root);
	}

	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
	
		Node* newnode = new Node(root->_kv);
		// 递归地拷贝原始根节点的左子树,并将结果赋给新节点的左指针
		newnode->_left = copy(root->_left);
		// 递归地拷贝原始根节点的右子树,并将结果赋给新节点的右指针
		newnode->_right = copy(root->_right);
		// 新节点的父节点默认为空
		newnode->_parent = nullptr;
		// 如果新节点的左子节点存在,设置其父节点为新节点
		if (newnode->_left)
		{
			newnode->_left->_parent = newnode;
		}
		// 如果新节点的右子节点存在,设置其父节点为新节点
		if (newnode->_right)
		{
			newnode->_right->_parent = newnode;
		}
		// 返回新树的根节点指针
		return newnode;
	}

	AVLTree<K, V>& operator=(const AVLTree <K, V> t)
	{
		
		this->swap(_root, t._root);
		return *this;
	}

	~AVLTree()
	{
		Destroy(_root);
	}

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

		Destroy(root->_left);
		Destroy(root->_right);
		
		delete root;
		root = nullptr;
	}


	//插入
	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 (cur->_kv.first < kv.first)
			{
				//往右走
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				//往左走
				parent = cur;
				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)
		{
			//新增节点在parent的左子树
			if (cur == parent->_left)
			{
				parent->_bf--;//平衡因子--
			}
			else
			{
				//新增节点在parent的右子树
				parent->_bf++;//平衡因子++
			}

			//检查是否需要向上更新祖先的平衡因子
			if (parent->_bf == 0)
			{
				//更新结束
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//新增的插入结点后,parent所在的子树符合平衡要求
				//但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//新增的插入结点在高的那边,parent所在的子树高的那边更高了
				// 破坏了平衡,parent所在的子树不符合平衡要求
				// 需要旋转处理
				if (parent->_bf == -2 && cur->_bf ==-1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(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 RotateR(Node* parent)
	{
		 //旋转
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;

		//更改_parent的指向
		if (subLR)//可能高度为0,如情况1
		{
			subLR->_parent = parent;
		}

		//提前记录parent的父亲节点,因为parent也可能是一整棵树中局部子树的根
		Node* Pparent = parent->_parent;
	
		subL->_right = parent;
		parent->_parent = subL;


		//parent之前可能是整棵树的根,也可能是一整棵树中局部的子树的根
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//如果parent之前只是一颗树中局部的子树的根
			//我们就需要提前记录parent的_parent
			if (Pparent->_left == parent)
			{
				Pparent->_left = subL;
			}
			else
			{
				Pparent->_right = subL;
			}

			subL->_parent = Pparent;

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

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

		parent->_right = subRL;

		//更新_parent的指向
		if (subRL)//可能高度为0
		{
			subRL->_parent = parent;
		}

		//	//提前记录parent的父亲节点,因为parent之前也可能是一整棵树中局部的子树的根
		Node* Pparent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//parent之前可能是整棵树的根,也可能是一整棵树中局部的子树的根
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			//如果parent之前只是一颗树中局部的子树的根
		   //我们就需要提前记录parent的_parent
			if (Pparent->_left == parent)
			{
				Pparent->_left = subR;
			}
			else
			{
				Pparent->_right = subR;
			}

			subR->_parent = Pparent;

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

		}
	}

	


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

		int bf = subLR->_bf;

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

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

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

		int bf = subRL->_bf;

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

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

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

		return nullptr;
	}

	int Height()
	{
		return _Height(this->_root);
	}

	int Size()
	{
		return _Size(this->_root);
	}
	/* 
	bool isBalanceTree()
	{
		return IsBalanceTree(this->_ root);
	}*/

	void inOrder()
	{
		InOrder(this->_root);

		cout << endl;
	}


private:

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

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

	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;
	}

	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;
		}

		// root的左和右如果都是AVL树,则该树一定是AVL树
		return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);

	}

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

		return _Size(root->_left) + _Size(root->_right) + 1;
	}

private:
		Node* _root = nullptr;
};

总结:

AVL的旋转难题,我们已经解决了,下面我们就要进入红黑树了,红黑树也是一个平衡二叉搜索树,就让我们一起去看看是怎么回事吧。

感谢各位大佬的观看,创作不易,还请各位大佬点赞支持~

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

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

相关文章

编译却出现Error: C9555E: Failed to check out a license.问题解决

学stm32的时候&#xff0c;照着操作突然发现编译报错。 去网上搜索答案的时候发现了这篇文章&#xff1a;参考博客:KEI5许可证没到期&#xff0c;编译却出现Error: C9555E: Failed to check out a license.问题解决 根据网上找的资料发现应该是版本问题导致的。在上一篇博客…

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…

【纯血鸿蒙】安装hdc工具

这里我先写Mac版的,Windows的在下面 首先要知道你的SDK安装在哪里了,不知道的话,可以打开DevEco Studio,打开设置页面里的HarmonyOS SDK,这个我们之前配置环境变量的时候用过。 其实主要是用到这里toolchains下的hdc命令。 所以我们需要配置环境变量。 1、打开Mac下的…

关于OpenFeign中@GetMapping不支持Java对象传递

原因&#xff1a;在使用openfeign远程调用的时候&#xff0c;使用get请求时候发现&#xff0c;如果feign里面传的是实体&#xff0c;当然接口里面也是需要传实体&#xff0c;但是发现出错了&#xff0c;后来去官网找了一下&#xff0c;发现get请求不支持传入Java实体对象&#…

网络原理之 TCP解释超详细!!!

TCP 有连接的 可靠传输 面向字节流 全双工 其中最核心的是可靠传输 那么 TCP 如何使用可靠传输的 ??? 我们尽可能传过去, 如果传不过去,发送方至少知道自己没传过去, 所以在于接收方, 收到或者没有收到, 都会有应答的操作 1. 确认应答 实现可靠性最核心的机制!!! 引出 …

一种解决Leaflet中Divicon城市气温标注空间重叠的办法

目录 前言 一、一些解决办法 1、marker的聚类 2、使用leaflet-canvas-label 3、使用Zoom和样式控制 二、基于rbush和Leaflet.LayerGroup.Collision的解决办法 1、关于rbush 2、Leaflet.LayerGroup.Collision.js 三、解决标签重叠的具体实现 1、添加元数据 2、添加到…

理解OAuth2与用户账户与授权UAA的关系

目录 关于OAuth2OAuth2的核心组件授权流程授权模式使用场景优点与缺点 关于UAA技术解释 UAA与OAuth2的关系 关于OAuth2 ‌‌OAuth2&#xff08;开放授权2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序访问用户资源&#xff0c;而无需共享用户的用户名和密码…

c语言中值调用(call by value)方式和引用调用(call by reference)

在C语言中参数传递主要有两种方式&#xff1a;通过值调用&#xff08;call by value&#xff09;和通过引用调用&#xff08;call by reference&#xff09;。 通过值调用&#xff08;Call by Value&#xff09; 说明&#xff1a;当使用值调用时&#xff0c;函数接收到的是参数…

(三)第一个Qt程序“Qt版本的HelloWorld”

一、随记 我们在学习编程语言的时候&#xff0c;各种讲解编程语言的书籍中通常都会以一个非常经典的“HelloWorld”程序展开详细讲解。程序虽然简短&#xff0c;但是“麻雀虽小&#xff0c;五脏俱全”&#xff0c;但是却非常适合用来熟悉程序结构、规范&#xff0c;快速形成对编…

Linux--学习笔记

第一章、简单使用Linux 1. Linux系统基本概念 多用户的系统&#xff1a;允许同时有很多个用户登录系统&#xff0c;使用系统里的资源多任务的系统&#xff1a;允许同时执行多个任务严格区分大小写&#xff1a;命令&#xff0c;选项&#xff0c;参数&#xff0c;文件名&#x…

从零实现数据结构:一文搞定所有排序!(下集)

1.快速排序 思路框架&#xff1a; 在有了前面冒泡选择插入希尔排序之后&#xff0c;人们就在想能不能再快一点&#xff0c;我们知道排序算法说人话就是把大的往后放小的往前放&#xff0c;问题就在于如何更快的把大的挪到数组队尾小的挪到数组前面。这里我们先总结一下上集前…

jenkins 自动化部署Springboot 项目

一、安装docker 1.更新yum命令 yum -y update2.查看机器有残留的docker服务&#xff0c;有就卸载干净 查看docker 服务 rpm -qa |grep docker卸载docker sudo yum remove docker-ce docker-ce-cli containerd.io sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/contai…

算法的学习笔记—二叉树的深度(牛客JZ55)

&#x1f600;前言 在二叉树的相关操作中&#xff0c;计算树的深度是一个非常基础但十分重要的操作。本文将详细解析如何计算一棵二叉树的深度&#xff0c;并通过代码实现来展示具体的解决方案。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f49d;二叉树的深度…

了解 .NET 8 中的定时任务或后台服务:IHostedService 和 BackgroundService

IHostedService.NET 8 引入了使用和管理后台任务的强大功能BackgroundService。这些服务使长时间运行的操作&#xff08;例如计划任务、后台处理和定期维护任务&#xff09;可以无缝集成到您的应用程序中。本文探讨了这些新功能&#xff0c;并提供了实际示例来帮助您入门。您可…

HarmonyOS开发 - 本地持久化之实现LocalStorage实例

用户首选项为应用提供Key-Value键值型的数据处理能力&#xff0c;支持应用持久化轻量级数据&#xff0c;并对其修改和查询。数据存储形式为键值对&#xff0c;键的类型为字符串型&#xff0c;值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。 说明&#x…

同步电机不同电流参考方向下的功率计算

同步电机的功率计算有时候会看见两种表达方式&#xff1a; 当以发电机惯例&#xff0c;即电流方向输出时&#xff0c;功率计算式为&#xff1a; { P s 3 2 ( u s d i s d u s q i s q ) Q s 3 2 ( u s q i s d − u s d i s q ) \left\{\begin{array}{l} P_{\mathrm{s}}\fr…

PostgreSQL(十三)pgcrypto 扩展实现 AES、PGP 加密,并自定义存储过程

目录 一、pgcrypto 简介1.1 安装 pgcrypto 扩展1.2 pgcrypto 包含的函数 二、用法①&#xff1a;对称加密&#xff08;使用 AES、Blowfish 算法&#xff09;2.1 密钥2.2 密钥偏移量 三、用法②&#xff1a;PGP加解密3.1 什么是PGP算法&#xff1f;3.2 使用 GPG 生成密钥对3.3 列…

【AI大模型】深入解析 存储和展示地理数据(.kmz)文件格式:结构、应用与项目实战

文章目录 1. 引言2. 什么是 .kmz 文件&#xff1f;2.1 .kmz 文件的定义与用途2.2 .kmz 与 .kml 的关系2.3 常见的 .kmz 文件使用场景 3. .kmz 文件的内部结构3.1 .kmz 文件的压缩格式3.2 解压缩 .kmz 文件的方法3.3 .kmz 文件的典型内容3.4 .kml 文件的结构与主要元素介绍 4. 深…

豆包MarsCode Agent 登顶 SWE-bench Lite 评测集

大语言模型&#xff08;LLM&#xff09;能力正在迅速提升&#xff0c;对包括软件工程在内的诸多行业产生了深远影响。GPT-4o、Claude3.5 等 LLM 已经逐步展现出胜任复杂任务的能力&#xff0c;例如文本总结、智能客服、代码生成&#xff0c;甚至能够分析和解决数学问题。在这一…

为什么在网络中不能直接传输数据

为什么在网络中不能直接传输数据 原因 在网络中不能直接传输原始数据形式&#xff0c;主要有以下几方面原因&#xff1a; 数据表示的多样性&#xff1a;不同的计算机系统、编程语言和应用程序对数据的表示方式可能各不相同。例如&#xff0c;整数在不同的编程语言中可能有不同…