C++进阶-->AVL树的实现

news2024/11/5 17:21:47

1. AVL树的介绍

1、AVL树的名字来源于他的发明者G. M. Adelson-Velsky和E. M. Landis两个前苏联的科学家,他们名字首元素组成。

2、AVL树就是我们前面二叉搜索树实现的时候提到的平衡二叉搜索树即二叉搜索树的左右孩子都是AVL树,即左右子树的高度差的绝对值不超过1。AVL树是一棵高度平衡二叉搜索树,通过高度差来控制平衡,从而引入了一个关键词--平衡因子。

3、每个结点都有平衡因子,平衡因子的计算方法为:右子树的高度减去左子树的高度,其实左子树减去右子树也可以,只不过后面的实现的一些逻辑关系需要变化一下。且任何的平衡因子的值都为0/1/-1,如果为2/-2则需要进行旋转操作。AVL树的实现并不一定需要平衡因子,但有了平衡因子我们就可以观察他的高度变化从而对不同情况进行不同操作。

4、有人可能疑惑,为什么AVL树的左右子树高度差绝对值不超过1,为什么不能为0,那我们想想看,会不会有些场景是无法做到高度差不超过0的,如下图所示:

5、AVL树整体结构和完全二叉树类似,因为左右子树高度差不超过1,那么就说明只有最后一行是不完整的;且树的整体高度可以控制在logN,增删查改也为O(logN)。


2. AVL树的实现

2.1 AVL树大体框架的设计

因为AVL树是key_value结构的,那么我们就需要定义一个pair键值对进行存储key和value的值,然后还有个存储平衡因子(balance factor)的值的变量,还需要一个找到parent的指针;以下是代码实现。

树结点的代码:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

//实现一个key_val结构
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;
};

2.2 AVL树的插入insert实现

2.2.1 AVL树插入大概思路

1、插入和前面二叉搜索树一样,比当前父亲节点的值大就往右走,反则往左走。

2、重点不在插入,而是在更新平衡因子,并且新增结点后,只会影响插入结点的全部祖先或者是部分祖先的平衡因子,例如插入的值对于_root结点来说是右子树,那么左子树的平衡因子不会受到影响。

3、更新平衡因子过程如果出现不平衡,那就需要进行旋转,我们前面说了AVL树的左右子树高度差不超过1,那么如果超过1即为2的时候就出现不平衡,所以要进行旋转。

2.2.2 更新平衡因子

更新规则:

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

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

3、插入结点在当前结点(parent结点)的左子树,那么当前结点(parent结点)的bf--,新增结点在当前结点(parent结点)的右子树,那么当前结点(parent结点)的平衡因子bf++。

4、parent的平衡因子的变化决定是否需要继续往上面的祖先的平衡因子进行修改。

更新停止条件:

1、如果parent的平衡因子等于0,那么说明当前parent的左右子树高度差为0,意味高度相等,树平衡,所以不需要再网上更新,举个例子,一开始树是一边高一边低,然后低的那一边增加了结点,即高度增加了,但他们原本的高度就是以最高的高度为标准,所以低的那边高度增加了不影响树的高度。

2、更新后如果parent的平衡因子等于1或者-1,则说明更新前parent的平衡因子的值是0,也意味着更新前以parent为root的树本身是平衡的,左右子树的高度一致,更新后树的整体高度发生变化,那就需要往上判断祖先的左右子树的整体高度有无变化,没有变化则不需要在往上走,如果有变化则需要接着往上看祖先的祖先的平衡因子。

3、更新后如果parent的平衡因子为2或者-2,说明本身树的高度就是一边高一边低,但是新增的结点在高的子树处,那就意味着高的更高了,本身树的高度差为1或-1,但现在为2或者-2,这不符合AVL树的特性(即左右子树的高度差不超过1或者-1)就需要进行旋转。(旋转下面讲这里提一下。)


代码实现如下,重点在“”分割线处,上面的部分代码是前面二叉搜索树所实现过的,具体思路可以翻一下前面那一篇博客:

	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了
		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->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			//对parent的平衡因子修改完毕,也有可能对祖先的平衡因子有影响,那么我们就需要
			//判断parent的平衡因子的变化情况再对parent的parent进行修改

			//bf等于0说明平衡了。
			if (parent->_bf == 0)
			{
				break;
			}

			//发现是1或者-1,那就说明bf是从0变过来的,那就意味着高度发生变化,那么就需要对
			//parent的parent进行修改
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}

			//为2了,不为AVL树的结构,就需要开始旋转
			//旋转在下面会讲。
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//旋转
				break;
			}

			//这个是为了如果说_bf给出一些奇怪的值的时候就在这里直接断掉。
			else
			{
				assert(false);
			}
		}
		return true;
	}

2.3 旋转

2.3.1 旋转的规则

1、保证二叉搜索树的规定没被改变,即比根结点大的放右边,比根结点小的放左边。

2、让旋转的树从不满足变平衡,降低旋转树的高度。

旋转分为右单旋、左单旋、左右双旋、右左双选

2.3.2 右单旋

要使用右单旋首先是因为左子树的高度比右子树的高度高,那么我们就需要让在右边的数据往下走,左边的数据往上走,这样高度就会平衡。给个图做实例:

这样旋转的本质其实就是让高度高的往上走,a的高度高,我们不可能让这个单一的子树往上走,所以我们只能移动他的根结点subL,就让subL成为这整棵树的根节点即可,那么a就会往上走,高度自然就降低了。

然后我们再看单纯是subL走到根节点处不行,我们还要让原来的根节点parent让出位置才可以,那我们分析,parent肯定是比左子树subL的子结点都大,那么我们就可以让parent走到subL的右子树处,但右子树还有一个subLR,我们再分析看看subLR能放到哪里,因为parent的值肯定比左子树subL所有的子结点都大,那么就可以让subLR放到parent的左边。这样做树的结构没有被破坏,还让高度平衡了。

最后我们还需要改变他们的平衡因子即可完成右单旋。

上图是抽象图,抽象图即可包含所有情况,这里就不举单一的情况出来例如h = 0的时候等等情况,因为在抽象图里都可以演示出来,代入即可,实现代码如下:

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


		parent->_left = subLR;
		//首先判断subLR是否为空,不为空就让parent->left 指向它
		if (subLR)
		{
			subLR->_parent = parent;
		}

		//如果这棵树只是一个子树,那就需要保存上一个树结点
		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		//如果这棵树不是某棵树的子树
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}

		//如果是某棵树的子树
		else
		{
			//判断是在某棵树的左子树还是右子树
			//左子树
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}

			//右子树
			else
			{
				pParent->_right = subL;
			}
			subL->_parent = pParent;
		}
		subL->_bf = 0;
		parent->_bf = 0;
	}

代码解析:

1、第一个if条件if(subLR)为空的时候,即h  = 0的时候,我们就不能对subL解引用,不然就会造成对空指针解引用,程序会崩溃。

2、定义一个pParent的用处是,如果说该图的树只是一棵子树,我们在进行旋转的时候改变了_root值从parent改成subL,那我们还需要让pParent的left或者right的指针指向subL,然后subL的parent指针指向pParent,如下图所示:

所以还要在下面进行pParent->_left ==parent或者pParent->_right==parent的判断。

3、最后就是更新平衡因子。


2.3.3 左单旋

左单旋的实现步骤也是一样,我们这里就不过多讲述,直接上图和代码,只要理解了右单旋,也就可以搞定左单旋。同样的思路,因为右子树的高度大于左子树的高度,那么我们就需要让右子树往上走(这个是我自己总结出来的思路方便我自己记忆hhhh)然后parent的那一棵树就需要往下走。

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

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

		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

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

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

代码可能和右旋有一点点不一样,在右单旋处pParent就是parentParent的缩写。

还有一点就是第二个if判断条件,右单旋是判断parent是否是root,而左单旋是判断parentParent是否为nullptr,其实实质上是一样的,如果parent为root的话,那么parentParent肯定是nullptr,如果parent不为root的话就说明parent整棵树是一棵子树,那么就肯定有parent结点,所以parentParent不为空。

最后就是通过图观察发现使用右单旋的时候parent的bf为-2,cur的bf为-1,使用左单旋的时候parent的bf等于2,cur的bf等于1,那么这在旋转的时候也还要判断一下,因为他们的使用条件不一样。


2.3.4 左右双旋

左右双旋顾名思义左旋再右旋,但为什么需要这个呢?我们用一个例子进行解析;

我们看到下面那一组图,发现左子树高,使用右单旋发现还是不平衡,并且变成了右子树高。我们观察发现,要使用右单旋的时候必须是单纯的一边高,由上面那一组图可以看见,右单旋是可以平衡树的,而下面一组图不是单纯的一边高。这时候就需要左右双旋进行解决。

左右双旋的旋转步骤:

由上面分析我们可以看出,使用单旋必须是一边高,那么我们可以构造一个一边高出来,假设没有10这个结点,5就是root结点,这样就是一边高了,右边高对5使用左单旋,完成后就发现,我们以10为_root的方面看,这棵树又成了左边一边高,那么我们只需要对10进行右单旋即可完成平衡。

这里添加null是为了更方便观察,因为我刚学的时候对于这个结构进行左右双旋真的很头疼,因为需要凭空想象出一个结点来进行单旋,那倒不如直接画几个null结点在上面更方便观察。所以左右双旋就这样完成了,最后还剩平衡因子没有解决。

解决平衡因子更新问题:

双旋的旋转操作简单,但是更新平衡因子才是难点,我们需要分多个类进行讨论,我们先以两个场景来引出这个问题。

由上三个场景我们可以看出,插入数据在8的左边和右边、还有插入8本身,他们的平衡因子都会因此改变。我们观察发现,场景1、2、3的8的位置的平衡因子不同,10和5的平衡因子也会跟着受影响,所以说我们要根据8的平衡因子来确定10和5的平衡因子的更新规律。

设:10为parent、5为subL、8为subLR(我忘记在图上画了,偷个懒)

场景一:当subLR->bf==1的时候,双旋后: subLR->bf== 0 、subL->bf== -1 、parent->bf ==0;

场景二:当subLR->bf ==-1的时候,双旋后: subLR->bf== 0 、subL->bf== 0 、parent->bf ==-1;

场景三:当subLR->bf ==0的时候,双旋后: subLR->bf== 0 、subL->bf== 0 、parent->bf ==0;

完成平衡因子更新问题后就可以实现左右双旋了。总结实现左右双旋的两个步骤:1、进行双旋

2、进行平衡因子的更新。实现代码如下可见:

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

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

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

2.3.5 右左双旋

右左双旋的实现逻辑也一样,这里就不过多讲述了上代码:

	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 = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

最后补充一点:就是为什么加一个else语句然后assert(false),这样是避免如果bf的值不为0不为1不为-1的时候直接报错,那么我们就知道问题就是出在bf的值身上。


2.3.6 完善insert

可能有人会注意到2.2处的insert的实现是不完整的,因为那里还没有讲旋转,这里我们讲完了之后我们可以一一观察,根据parent和cur的平衡因子的不同来确定是使用右单旋、左单旋、还是左右双旋、还是右左双旋。

右单旋:parent的平衡因子等于-2,cur的平衡因子等于-1。

左单旋:parent的平衡因子等于2,cur的平衡因子等于1。

左右单旋:parent的平衡因子等于-2,cur的平衡因子等于1。

右单旋:parent的平衡因子等于2,cur的平衡因子等于-1。

这些都是可以根据上面画的图来分析出来的。这里就直接上完整版的insert的代码了:

	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了
		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->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			//对parent的平衡因子修改完毕,也有可能对祖先的平衡因子有影响,那么我们就需要
			//判断parent的平衡因子的变化情况再对parent的parent进行修改

			//bf等于0说明平衡了。
			if (parent->_bf == 0)
			{
				break;
			}

			//发现是1或者-1,那就说明bf是从0变过来的,那就意味着高度发生变化,那么就需要对
			//parent的parent进行修改
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}

			//为2了,不为AVL树的结构,就需要开始旋转
			//旋转在下面会讲。
			else if (parent->_bf == 2 || parent->_bf == -2)
			{

				//右单旋
				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;
			}

			//这个是为了如果说_bf给出一些奇怪的值的时候就在这里直接断掉。
			else
			{
				assert(false);
			}
		}
		return true;
	}

3.测试代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include"AVLTree.h"

void TestAVLTree1()
{
	AVLTree<int, int> t;
	// 常规的测试用例
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto e : a)
	{
		t.Insert({ e, e });
	}

	t.InOrder();
	cout << t.IsBalanceTree() << endl;
}

// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	cout << "Insert:" << end2 - begin2 << endl;
	cout << t.IsBalanceTree() << endl;

	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}
	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/
	size_t end1 = clock();
	cout << "Find:" << end1 - begin1 << endl;
}


int main()
{
	TestAVLTree2();

	return 0;
}

4. 完整的AVL树实现的代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

//实现一个key_val结构
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:
	//insert
	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了
		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->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			//对parent的平衡因子修改完毕,也有可能对祖先的平衡因子有影响,那么我们就需要
			//判断parent的平衡因子的变化情况再对parent的parent进行修改

			//bf等于0说明平衡了。
			if (parent->_bf == 0)
			{
				break;
			}

			//发现是1或者-1,那就说明bf是从0变过来的,那就意味着高度发生变化,那么就需要对
			//parent的parent进行修改
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}

			//为2了,不为AVL树的结构,就需要开始旋转
			//旋转在下面会讲。
			else if (parent->_bf == 2 || parent->_bf == -2)
			{

				//右单旋
				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;
			}

			//这个是为了如果说_bf给出一些奇怪的值的时候就在这里直接断掉。
			else
			{
				assert(false);
			}
		}
		return true;
	}

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


		parent->_left = subLR;
		//首先判断subLR是否为空,不为空就让parent->left 指向它
		if (subLR)
		{
			subLR->_parent = parent;
		}

		//如果这棵树只是一个子树,那就需要保存上一个树结点
		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		//如果这棵树不是某棵树的子树
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}

		//如果是某棵树的子树
		else
		{
			//判断是在某棵树的左子树还是右子树
			//左子树
			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;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

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

		parent->_bf = subR->_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 == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_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 = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

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

	int Size()
	{
		return _Size(_root);
	}

	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}

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

		return nullptr;
	}

private:
	Node* _root = nullptr;

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

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

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

	bool _IsBalanceTree(Node* root)
	{
		// 空树也是AVL树
		if (nullptr == root)
			return true;
		// 计算pRoot结点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;

		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		// pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "高度差异常" << endl;
			return false;
		}

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

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

END!

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

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

相关文章

【网络安全】|nessus使用

1、扫描结果分析&#xff1a; Sev&#xff1a;漏洞的严重性级别 CVSS&#xff1a;量化漏洞严重性的标准&#xff0c;通过计算得出一个分数&#xff0c;分数越高表示漏洞越严重。 VPR&#xff1a;基于风险的评分系统&#xff0c;帮助组织优先处理风险最高的漏洞。 EPSS&#xf…

P2-5【C语言基本数据类型、运算符和表达式】第五节-知识要点:格式输出函数printf()

讲解视频&#xff1a; P2-5【C语言基本数据类型、运算符和表达式】第五节-知识要点&#xff1a;格式输出函数printf() 知识要点&#xff1a;格式输出函数printf()。 一、任务分析 已知三角形三边a&#xff0c;b&#xff0c;c的值&#xff0c;求三角形的面积。要求输出a&#…

RFID资产管理

随着物联网和智能制造的发展&#xff0c;RFID资产管理逐渐成为企业提升运营效率的重要工具。利用RFID技术&#xff0c;企业能够实时跟踪和管理各种固定资产&#xff0c;从而提高资产利用率&#xff0c;降低运营成本。在现代化的管理体系中&#xff0c;RFID资产管理不仅限于资产…

Vue2——单页应用程序路由的使用

一.单页应用程序与多页应用程序之间的比较 二.单页的应用场景 系统类网站 / 内部网站 / 文档类网站 / 移动端网站 三.路由的介绍 1. 什么是路由 路由是一种映射关系 2. Vue中的路由是什么 路径和组件的映射关系 四.VueRouter的使用 5个基础步骤&#xff08;固定&#xff09; …

苹果ipa上架apple store 遇到的问题汇总已经解决方案!

大家伙&#xff0c;我是小黄。 最近在将ipa上架到apple store的时候遇到了一些问题&#xff0c;经过很长时间的摸索和修改终于成功上架了&#xff0c;下面是我遇到的问题和解决过程&#xff0c;希望可以帮助到大家。 一&#xff1a; Guideline 1.3 - Safety - Kids Category …

数据库三范式(1NF、2NF、3NF)

1NF&#xff08;第一范式&#xff09; 定义&#xff1a;确保每一列都是原子值&#xff0c;即是不可分割的基础数据项。 所谓第一范式&#xff08;1NF&#xff09;是指在关系模型中&#xff0c;对于添加列的一个规范要求&#xff0c;所有的列都 应该是原子性的&#xff0c;即数…

亚马逊CEO安迪·贾西(Andy Jassy)近日透露,Alexa助手即将迎来一次重大升级,具备“代理性”功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

ts:函数的重载

ts&#xff1a;函数的重载 1 主要内容说明2 例子2.1 函数的重载2.1.1 源码1 &#xff08;函数的重载&#xff09;2.1.2 源码1运行效果 3.结语4.定位日期 1 主要内容说明 重载函数可以提高类型安全性&#xff0c;内容灵活性和可读性。重载允许同一个函数定义多个参数类型的数量…

qt QTabWidget详解

1、概述 QTabWidget是Qt框架中的一个控件&#xff0c;它提供了一个标签页式的界面&#xff0c;允许用户在不同的页面&#xff08;或称为标签&#xff09;之间切换。每个页面都可以包含不同的内容&#xff0c;如文本、图像、按钮或其他小部件。QTabWidget非常适合用于创建具有多…

telnet 密码模式 访问路由器

telnet 密码访问华为路由器 模拟被访问路由 sy [Huawei]int g0/0/0 //选中 g0/0/0端口 [Huawei-GigabitEthernet0/0/0]ip add 192.168.1.1 24 //设置端口ip [Huawei]user-interface vty 0 4 //配置vty [Huawei-ui-vty0-4]set authentication password cipher huawei123 //设置…

项目模块1~12总结:服务器大模块梳理

一、思维导图 二、设计思路 1、各种回调函数梳理 服务器里面包含了监听套接字和监听到的通信套接字&#xff08;新连接&#xff09;&#xff0c;我们要对这两种套接字进行设置回调函数&#xff0c;其中监听套接字里面只要设置读回调&#xff0c;通信套接字要设置5种回调&…

UE4安卓Gradle工程中的libUE4.so的生成原理

流程图 流程图放在最前面&#xff0c;下面是讲解。 libUE4.so 问&#xff1a;在UE4安卓开发中&#xff0c;libUE4.so即是符号表&#xff0c;又是引擎代码native&#xff0c;是吗&#xff1f; 答&#xff1a;是的&#xff0c;libUE4.so在UE4安卓开发中既包含符号表&#xff0c;…

Linux下Nginx的安装与使用

Linux下Nginx的安装与使用 博客&#xff1a; www.lstar.icu 开源地址 Gitee 地址&#xff1a; https://gitee.com/lxwise/iris-blog_parent Github 地址&#xff1a; https://github.com/lxwise/iris-blog_parent 序言 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子…

STM32 HAL库 SPI驱动1.3寸 OLED屏幕

目录 参考硬件引脚与接线 点亮屏幕CubeMX 配置OLED 驱动程序代码 参考 基于STM32F103C8T6最小系统板HAL库CubeMX SPI驱动7针 OLED显示屏&#xff08;0.96寸 1.3寸通用&#xff09;0.96 oled HAL库驱动 SPI STM32SPI驱动0.96/1.3寸 OLED屏幕&#xff0c;易修改为DMA控制STM32驱…

江协科技STM32学习- P26 UART串口外设

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

基于Abaqus的高纯铝不同应变率下单晶塑性变形的取向依赖性研究

文章题目&#xff1a;《Strain rate effect of high purity aluminum single crystals: Experiments and simulations》 文章doi&#xff1a;10.1016/j.ijplas.2014.10.002 推荐理由&#xff1a;作者研究了高纯铝不同应变率下单晶塑性变形的取向依赖性&#xff0c;不同应变率…

虚拟现实与增强现实:重塑娱乐和教育的边界!

内容概要 在这个瞬息万变的时代&#xff0c;虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;正如两位魔法师&#xff0c;腾云驾雾间掀起了一场教育与娱乐的革命。虚拟现实带我们飞跃平凡&#xff0c;进入一个充满奇迹的数字宇宙&#xff0c;仿佛我们…

【论文分享】利用机器学习和计算机视觉技术增强城市街道峡谷中的PM2.5实时监测

本文以兰州市为研究区域使用计算机视觉技术从交通摄像头拍摄的交通图像中提取实时交通流量和街景特征&#xff0c;以预测PM2.5浓度&#xff0c;并解释道路环境变化对PM2.5水平的影响。 【论文题目】 Enhancing urban real-time PM2.5 monitoring in street canyons by machine…

《双指针篇》---复写零(标的简单,实际比较复杂)

题目传送门 方法一&#xff1a;双指针 我们发现不能从前向后复写&#xff0c;因为会覆盖掉还没有被复写的数据 因此我们需要从后往前复写。 此时left应该指向最后一个被复写的数。 right指向数组最后一个元素 1.找到最后一个被复写的数据。我们用left指向 ①先判断left的位置0/…

Docker(二):Docker的基本使用

1 Docker的基本使用 1.1 镜像相关操作 1、从DockerHub搜索镜像 [rootmaster ~]# docker search centos # 镜像名字 描述 星标 是否官方&#xff08;有OK表示为官方镜像&#xff09; NAME …