【数据结构】AVLTree实现详解

目录

一.什么是AVLTree

二.AVLTree的实现

1.树结点的定义

2.类的定义

3.插入结点

①按二叉搜索树规则插入结点

②更新平衡因子

更新平衡因子情况分析

③判断是否要旋转

左单旋

右单旋

左右单旋

右左双旋

4.删除、查找和修改函数

查找结点

三.测试

1.判断是否是搜索树

2.判断每一个结点的左右子树高度差是否不大于1

四.所有源代码

AVLTree.h

Test.c
在本篇博客中,作者将会使用C++来带领你理解和实现AVLTree,同时,在学习AVLTree之

前,你可以先看看下面这篇二叉搜索树的博客,因为AVTree也是一种二叉搜索树,它们有一些规则是一样的。

【C++】二叉搜索树-CSDN博客

一.什么是AVLTree

在二叉搜索树的博客中,我们提到了,当插入的数据有序或者接近有序的时候,二叉搜索树退化单支树,导致其效率变低,所以为了解决这种情况,于是我们提出了AVLTree,即二叉平衡搜索树。


如下图就是一棵二叉搜索树退化成的单支树。


那么AVLTree又是如何实现使二叉树搜索树不会退化成单支树的呢,它又是如何保证效率的呢?

因为AVLTree严格的要求左右子树的高度差****不能大于1,且每一棵子树也一样。 如下图所示。

通过这样严格的高度差要求,该树就可以达到一个非常平衡的状态,以致于它的插入、删除、查找等操作的效率非常的高,其时间复杂度就是O(logN),N为结点的个数。


那么它又是如何实现这样的要求的?

前面说了,AVLTree保证了每个结点的左右子树的高度差不大于1,那是因为当有一个结点的左右子树高度差为2时,它会进行旋转使其保证平衡,至于怎么旋转,我们接着往下看。

二.AVLTree的实现

知道了AVLTree的规则,那么我们就可以来实现一下AVLTree了。

1.树结点的定义

在树结点的定义中,对比平常的二叉树,多了两个变量,一个是父结点的指针_parent,一个是**_bf平衡因子**。

其中**_parent指针**是方便我们在后续的操作中,去倒着往上去找父结点。

_bf平衡因子是存储该结点的左右子树的高度差,这里默认为右子树的高度减去****左子树的高度,通过_bf平衡因子,我们可以很快的知道该结点下的树是否平衡。

cpp 复制代码
	template<class T>
	struct TreeNode
	{
		//成员变量
		T _data;//树结点存储的数据类型
		TreeNode<T>* _left;//左孩子指针
		TreeNode<T>* _right;//右孩子指针
		TreeNode<T>* _parent;//父结点指针
		int _bf;//平衡因子

		//成员函数
		TreeNode(cosnt T& tmp)//构造函数
			:_data(tmp)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _bf(0)
		{}
	};

对于平衡因子来说,如下图所示。

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

当**_bf平衡因子绝对值大于1**时,说明该结点的左右子树高度差大于1,这个时候需要进行旋转处理。如下图所示。

2.类的定义

对于类的定义,就比较简单,就是一个**_root指针**指向根节点。

cpp 复制代码
	template<class T>
	class AVLTree
	{
		typedef TreeNode<T> Node;
	public:
        //构造函数
		AVLTree()
			:_root(nullptr)
		{}

	private:
		Node* _root;//根节点的指针
	};

3.插入结点

对于AVLTree来说,最重要的就是插入结点,同时,这也是比较难的一部分,代码也比较长,但是不要怕,我们把AVLTree的插入分成如下几个部分。

**1.**先按二叉搜索树的规则插入结点,就是先不管三七二十一,先把结点插入进去再说。

至于二叉搜索树的插入,可以看开头的博客链接。

**2.**更新平衡因子,插入新结点后会影响平衡因子,所以插入新结点后,要更新平衡因子。

**3.**判断是否要旋转,更新完平衡因子后,要判断直接结束了还是要进行旋转调整。


接下来我们要把上面的三个过程转换成代码,这里代码会有点长,我们把这个insert函数分成三部分来说。如下是这个函数的函数名参数tmp为要插入的值

cpp 复制代码
		bool insert(const T& tmp)

①按二叉搜索树规则插入结点

对于这一部分来说,没那么难,整个插入过程如下图所示。

就是用tmp从根结点开始进行比较,一直往下找空位,找到空位后,再把结点插入进去。


cpp 复制代码
			//如果根节点为空,则直接插入
			if (_root == nullptr)
			{
				_root = new Node(tmp);
				return true;
			}

			//如果根节点不为空,则先按二叉搜索树的规则进行插入
			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)//往下找空位
			{
				if (tmp > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (tmp < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			//找到空位后,给空位一个新结点
			cur = new Node(tmp);
			if (cur->_data > cur_parent->_data)
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}

②更新平衡因子

插入完结点后, 我们要更新平衡因子,那么怎么更新呢,我们往下看。

首先,当插入一个新结点的时候,只会影响新结点一路往上的_parent结点。如下图所示。

所以当我们更新平衡因子的时候,只需要通过_parent指针一路向上更新即可,但是也不一定一直更新到根节点,为什么呢?我继续往下看。


既然是更新平衡因子,那么怎么个更新呢?

在开头,我们提到**_bf平衡因子 = 右子树的高度 - 左子树的高度**。

那么看上图,cur为新结点,cur_parent为cur的父结点,对于cur_parent来说,它的左子树新增了一个结点,也就是说左子树的高度+1,所以对于**_bf = 右子树高度 - 左子树高度这个公式来说,左子树高度+1,即_bf-1,如果cur是cur_parent的右子树反之+1**。这里可能会很乱,没事,我们来看下图。

当更新完一个结点的平衡因子的时候,这个时候会有三种情况

**1.**更新完该结点后,会影响****向上的父结点继续更新

**2.**更新完该结点后,不会影响****向上的父结点更新结束

3.更新完该结点后,该结点的平衡因子==2或==-2,此时需要进行旋转处理

整个过程的代码如下。

cpp 复制代码
			//插入完结点后,要更新平衡因子
			while (cur_parent != nullptr)
			{
				if (cur_parent->_right == cur)//说明新增结点是cur_parent的右子树,即右子树高度增加1
				{
					cur_parent->_bf++;
				}
				else
				{
					cur_parent->_bf--;
				}

				if (cur_parent->_bf == 0)//如果cur_parent的平衡因子为0,结束更新
				{
					break;
				}
				else if (cur_parent->_bf == 1 || cur_parent-> == -1)//如果cur_parent的平衡因子为1或者-1,则继续向上更新
				{
					cur = cur_parent;
					cur_parent = cur_parent->_parent;
				}
				else if (cur_parent->_bf == 2 || cur_parent->_bf == -2)
				{
					//进行旋转处理
				}
更新平衡因子情况分析

更新完后,为什么平衡因子==0就停止更新==1或者==-1要继续向上更新

我们来分析一下。首先来看**==0情况**。如下图所示

我们插入20,插入后结点15的平衡因子要更新为0

从图中,我们可以轻易的看出对于结点10来说,它的平衡因子是不受影响的,那么是为什么呢?

因为树的高度高的子树来决定的,对于结点10来说,它的高的子树是**(15~12)这个两个结点构成的子树** ,而新增的结点20没有增加到高的子树上去,所以对于结点10来说,高度没变。如下图所示。

接下来看看==1或者==-1的情况,

首先是一棵正常的树,我们插入新结点20,插入后,结点18的平衡因子要变为1,

因为结点18的平衡因子为1,所以要继续向上更新,为什么呢?

解释与上面那种情况类似,当一个结点的平衡因子由0变为1或者-1的时候,说明该结点的高度增加了1,说明该结点的子树高度增加了1,这个时候会影响上面结点的高度,所以平衡因子要一直向上更新。

③判断是否要旋转

至于当平衡因子==2或者==-2的时候,要进行旋转处理就没什么好说的了,因为AVLTree就是严格的要求平衡因子的绝对值不能大于2。


那么旋转又是怎么旋转的呢?

旋转又可以分为四种情况:左单旋右单旋左右双旋右左双旋

我们来分析一下。

左单旋

一个结点的_bf为2,且它的右孩子的_bf为1时,要进行左单旋。 如下图所示

旋转完后,如下图所示。

对于左单旋的所有情况,我们可以用下面这两个抽象图来表示所有情况。

进行旋转处理后,如下图所示。

对于整个旋转过程来说,就是去改变cur_parentcur结点指针关系

cur_parent右指针,去指向cur的左子树

cur的左指针去指向cur_parent

注意,这里还要改变****cur_parentcur结点的**_parent指针**。


但是这里注意的时,cur_parent不一定如上图所示为根结点,它有可能是某个结点的子树,如下图所示。

所以旋转完成后还要进行一次判断是否需要cur结点链接到上一个结点

cpp 复制代码
		//左单旋	
		void RotateL(Node* cur_parent)
		{
			Node* cur = cur_parent->_right;
			Node* cur_left = cur->_left;

			//改变指针的链接关系
			cur_parent->_right = cur_left;
			if (cur_left != nullptr)
			{
				cur_left->_parent = cur_parent;
			}

			cur->_left = cur_parent;
			Node* cur_parent_parent = cur_parent->_parent;//提前保存cur_parent的父结点
			cur_parent->_parent = cur;

			//旋转完成后要判断cur_parent是否为根
			if (cur_parent_parent != nullptr)//说明cur_parent不是根
			{
				if (cur_parent_parent->_data < cur_parent->_data)
				{
					cur_parent_parent->_right = cur;
					cur->_parent = cur_parent_parent;
				}
				else
				{
					cur_parent_parent->_left = cur;
					cur->_parent = cur_parent_parent;
				}
			}
			else//说明cur_parent是根
			{
				_root = cur;
				cur->_parent = nullptr;
			}

			//旋转完成后,平衡因子调整为0
			cur_parent->_bf = cur->_bf = 0;
		}
右单旋

同样的右单旋与左单旋类似,只不过是反过来而已,这里不再做过多的解释,只给出抽象图出来。

经过右单旋后,变成如下图所示。

cpp 复制代码
		//右单旋
		void RotateR(Node* cur_parent)
		{
			Node* cur = cur_parent->_left;
			Node* cur_right = cur->_right;

			cur_parent->_left = cur_right;
			if (cur_right != nullptr)
			{
				cur_right->_parent = cur_parent;
			}

			cur->_right = cur_parent;
			Node* cur_parent_parent = cur_parent->_parent;
			cur_parent->_parent = cur;

			if (cur_parent_parent != nullptr)
			{
				if (cur_parent_parent->_data > cur_parent->_data)
				{
					cur_parent_parent->_left = cur;
					cur->_parent = cur_parent_parent;
				}
				else
				{
					cur_parent_parent->_right = cur;
					cur->_parent = cur_parent_parent;
				}
			}
			else
			{
				_root = cur;
				cur->_parent = nullptr;
			}

			cur_parent->_bf = cur->_bf = 0;
		}
左右单旋

左单旋和右单旋解释完成后,接下来要将双旋了,那么什么情况下要用双旋呢?

我们来看一下抽象图

当新插入结点时,如果符合上图中的情况,则要进行左右双旋操作,注意!!! 新增的红色结点也有可能插入在结点15的右树,其旋转过程就是先对结点10进行一个左单旋再对结点20进行一个右单旋。如下图所示。

先对结点10进行一个左单旋,再对结点20进行一个右单旋。


但是上面这个抽象图不能代表双旋的所有情况,因为双旋有一种特殊的情况,如下图所示。

对于这种特殊情况,我们只需要先前保存cur_right的平衡因子,双旋完成后,如果平衡因子为0,说明就是这种特殊情况,再进行特殊处理即可。

cpp 复制代码
		//左右双旋
		void RotateLR(Node* cur_parent)
		{
			Node* cur = cur_parent->_left;
			Node* cur_right = cur->_right;

			int bf = cur_right->_bf;//这里保存平衡因子的原因是,新增的结点有可能插入在左树,也有可能插入在右树,通过保存平衡因子来进行判断是哪一种情况

			//先对cur进行一个左单旋
			RotateL(cur);

			//再对cur_parent进行一个右单旋
			RotateR(cur_parent);

			//旋转完成后,要更新平衡因子
			if (bf == -1)//说明新增的结点是插入在左树
			{
				cur->_bf = 0;
				cur_parent->_bf = 1;
				cur_right->_bf = 0;
			}
			else if (bf == 1)//说明新增的结点是插入在右树
			{
				cur->_bf = -1;
				cur_parent->_bf = 0;
				cur_right->_bf = 0;
			}
			else if (bf == 0)//特殊情况
			{
				cur->_bf = 0;
				cur_parent->_bf = 0;
				cur_right->_bf = 0;
			}
		}
右左双旋

对于右左双旋来说,与左右单旋类似,就是换了个方向而已,这里不再过多的解释,直接给出抽象图和代码。

先进行一个右单旋,再进行一个左单旋,旋转如下图所示。

同样的,这里也要处理特殊情况,就不在过多的解释。

cpp 复制代码
		//右左双旋
		void RotateRL(Node* cur_parent)
		{
			Node* cur = cur_parent->_right;
			Node* cur_left = cur->_left;

			int bf = cur_left->_bf;

			//先对cur进行一个右单旋
			RotateR(cur);

			//再对cur_parent进行一个左单旋
			RotateL(cur_parent);

			//更新平衡因子
			if (bf == -1)
			{
				cur->_bf = 1;
				cur_parent->_bf = 0;
				cur_left->_bf = 0;
			}
			else if (bf == 1)
			{
				cur->_bf = 0;
				cur_parent->_bf = -1;
				cur_left->_bf = 0;
			}
			else if (bf == 0)
			{
				cur->_bf = 0;
				cur_parent->_bf = 0;
				cur_left->_bf = 0;
			}
		}

走到这一步,我们的旋转代码就已经全部写好了,接下来可以接着补充我们的插入函数的旋转部分

cpp 复制代码
		bool insert(const T& tmp)
		{
			//如果根节点为空,则直接插入
			if (_root == nullptr)
			{
				_root = new Node(tmp);
				return true;
			}

			//如果根节点不为空,则先按二叉搜索树的规则进行插入
			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)//往下找空位
			{
				if (tmp > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (tmp < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			//找到空位后,给空位一个新结点
			cur = new Node(tmp);
			if (cur->_data > cur_parent->_data)
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}

			//插入完结点后,要更新平衡因子
			while (cur_parent != nullptr)
			{
				if (cur_parent->_right == cur)
				{
					cur_parent->_bf++;
				}
				else
				{
					cur_parent->_bf--;
				}

				if (cur_parent->_bf == 0)
				{
					break;
				}
				else if (cur_parent->_bf == 1 || cur_parent->_bf == -1)
				{
					cur = cur_parent;
					cur_parent = cur_parent->_parent;
				}
				else if (cur_parent->_bf == 2 || cur_parent->_bf == -2)
				{
					//进行旋转处理
					if (cur_parent->_bf == 2)
					{
						if (cur->_bf == 1)//左单旋
						{
							RotateL(cur_parent);
						}
						else if(cur->_bf == -1)//右左双旋
						{
							RotateRL(cur_parent);
						}
					}
					else if (cur_parent->_bf == -2)
					{
						if (cur->_bf == 1)//左右单旋
						{
							RotateLR(cur_parent);
						}
						else if (cur->_bf == -1)//右单旋
						{
							RotateR(cur_parent);
						}
					}
					break;
				}
			}
			return true;
		}

写到这里,我们的插入代码就完成了,这也是AVLTree实现的最核心的部分。

4.删除、查找和修改函数

对于一个数据结构来说,最基本的操作是增删改查,现在增已经实现了,那么还有剩下的三个函数。

删除:

对于AVLTree的删除来说,情况比插入还稍许复杂,又由于博主的能力和精力有限,在这里就不讲了。

查找:

对于AVLTree的查找来说,就显得很简单,跟二叉搜索树的查找一样,就不过多的解释,等下下面直接给出代码。

修改:

对于AVLTree的修改来说,一般来说,是不允许修改的,因为AVLTree是一种搜索树,它的中序遍历是绝对的有序的,如果进行修改,就会破坏整棵树的性质导致不再是搜索树但是也有允许修改的情况,当树中存的是一个pair键值对,我们可以对pair键值对的val进行修改,具体解释,参考这篇博客中的kv模型【C++】二叉搜索树-CSDN博客

查找结点
cpp 复制代码
		//查找结点
		Node* find(const T& tmp)
		{
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (tmp > cur->_data)
				{
					cur = cur->_right;
				}
				else if (tmp < cur->_data)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

三.测试

既然我们的插入已经完成了,那么我们又如何判断我们写的代码是对的呢,接下来讲一下如何测试我们写的AVLTree。

1.判断是否是搜索树

既然是AVLTree,那么它一定是一棵搜索树,所以它的中序遍历一定是有序的,所以我们可以写一个中序遍历来看看是否是搜索树。

cpp 复制代码
		//中序遍历
		void InOrder()
		{
			Node* cur = _root;
			_InOrder(cur);
		}

		//中序遍历的子函数
		void _InOrder(Node* cur)
		{
			if (cur == nullptr)
				return;

			_InOrder(cur->_left);
			cout << cur->_data << " ";
			_InOrder(cur->_right);
		}

2.判断每一个结点的左右子树高度差是否不大于1

判断完了确定是搜索树,接下来就要判断是否是AVLTree树,即每一个结点的左右子树高度差都不大于1


首先,我们可以先写一个求树的高度的函数

cpp 复制代码
		//求树的高度
		int height(Node* cur)
		{
			if (cur == nullptr)
				return 0;
            
            //返回左右子树高的那一个子树+1,fmax是库函数,求两个参数的较大值
			return fmax(height(cur->_left), height(cur->_right)) + 1;
		}

然后就可以写一个判断是否平衡的函数

cpp 复制代码
		//判断是否平衡
		bool JudgeBanlance()
		{
			Node* cur = _root;
			return _JudgeBanlance(cur);
		}

		//判断是否平衡的子函数
		bool _JudgeBanlance(Node* cur)
		{
			if (cur == nullptr)
				return true;

			int left_height = height(cur->_left);//求cur左子树的高度
			int right_height = height(cur->_right);//求cur右子树的高度

            //abs是求左右子树高度差的绝对值
			return abs(left_height - right_height) < 2
				&& _JudgeBanlance(cur->_left)
				&& _JudgeBanlance(cur->_right);
		}

写到这里,我们的判断是否是AVLTree也完成了,AVLTree也基本实现了。

四.所有源代码

AVLTree.h

cpp 复制代码
#pragma once
#include<iostream>
#include<time.h>
using namespace std;
//编写一篇博客
namespace blog_AVLTree
{
	template<class T>
	struct TreeNode
	{
		//成员变量
		T _data;//树结点存储的数据类型
		TreeNode<T>* _left;//左孩子指针
		TreeNode<T>* _right;//右孩子指针
		TreeNode<T>* _parent;//父结点指针
		int _bf;//平衡因子

		//成员函数
		TreeNode(const T& tmp)//构造函数
			:_data(tmp)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _bf(0)
		{}
	};

	template<class T>
	class AVLTree
	{
		typedef TreeNode<T> Node;
	public:
		AVLTree()
			:_root(nullptr)
		{}

		//插入
		bool insert(const T& tmp)
		{
			//如果根节点为空,则直接插入
			if (_root == nullptr)
			{
				_root = new Node(tmp);
				return true;
			}

			//如果根节点不为空,则先按二叉搜索树的规则进行插入
			Node* cur = _root;
			Node* cur_parent = nullptr;
			while (cur != nullptr)//往下找空位
			{
				if (tmp > cur->_data)
				{
					cur_parent = cur;
					cur = cur->_right;
				}
				else if (tmp < cur->_data)
				{
					cur_parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			//找到空位后,给空位一个新结点
			cur = new Node(tmp);
			if (cur->_data > cur_parent->_data)
			{
				cur_parent->_right = cur;
				cur->_parent = cur_parent;
			}
			else
			{
				cur_parent->_left = cur;
				cur->_parent = cur_parent;
			}

			//插入完结点后,要更新平衡因子
			while (cur_parent != nullptr)
			{
				if (cur_parent->_right == cur)
				{
					cur_parent->_bf++;
				}
				else
				{
					cur_parent->_bf--;
				}

				if (cur_parent->_bf == 0)
				{
					break;
				}
				else if (cur_parent->_bf == 1 || cur_parent->_bf == -1)
				{
					cur = cur_parent;
					cur_parent = cur_parent->_parent;
				}
				else if (cur_parent->_bf == 2 || cur_parent->_bf == -2)
				{
					//进行旋转处理
					if (cur_parent->_bf == 2)
					{
						if (cur->_bf == 1)//左单旋
						{
							RotateL(cur_parent);
						}
						else if(cur->_bf == -1)//右左双旋
						{
							RotateRL(cur_parent);
						}
					}
					else if (cur_parent->_bf == -2)
					{
						if (cur->_bf == 1)//左右单旋
						{
							RotateLR(cur_parent);
						}
						else if (cur->_bf == -1)//右单旋
						{
							RotateR(cur_parent);
						}
					}
					break;
				}
			}
			return true;
		}

		//查找结点
		Node* find(const T& tmp)
		{
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (tmp > cur->_data)
				{
					cur = cur->_right;
				}
				else if (tmp < cur->_data)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

		//中序遍历
		void InOrder()
		{
			Node* cur = _root;
			_InOrder(cur);
		}

		//求树的高度
		int height(Node* cur)
		{
			if (cur == nullptr)
				return 0;

			return fmax(height(cur->_left), height(cur->_right)) + 1;
		}

		//判断是否平衡
		bool JudgeBanlance()
		{
			Node* cur = _root;
			return _JudgeBanlance(cur);
		}

	private:
			//左单旋	
			void RotateL(Node* cur_parent)
			{
				Node* cur = cur_parent->_right;
				Node* cur_left = cur->_left;

				//改变指针的链接关系
				cur_parent->_right = cur_left;
				if (cur_left != nullptr)
				{
					cur_left->_parent = cur_parent;
				}

				cur->_left = cur_parent;
				Node* cur_parent_parent = cur_parent->_parent;
				cur_parent->_parent = cur;

				//旋转完成后要判断cur_parent是否为根
				if (cur_parent_parent != nullptr)//说明cur_parent不是根
				{
					if (cur_parent_parent->_data < cur_parent->_data)
					{
						cur_parent_parent->_right = cur;
						cur->_parent = cur_parent_parent;
					}
					else
					{
						cur_parent_parent->_left = cur;
						cur->_parent = cur_parent_parent;
					}
				}
				else//说明cur_parent是根
				{
					_root = cur;
					cur->_parent = nullptr;
				}

				//旋转完成后,平衡因子调整为0
				cur_parent->_bf = cur->_bf = 0;
			}

			//右单旋
			void RotateR(Node* cur_parent)
			{
				Node* cur = cur_parent->_left;
				Node* cur_right = cur->_right;

				cur_parent->_left = cur_right;
				if (cur_right != nullptr)
				{
					cur_right->_parent = cur_parent;
				}

				cur->_right = cur_parent;
				Node* cur_parent_parent = cur_parent->_parent;
				cur_parent->_parent = cur;

				if (cur_parent_parent != nullptr)
				{
					if (cur_parent_parent->_data > cur_parent->_data)
					{
						cur_parent_parent->_left = cur;
						cur->_parent = cur_parent_parent;
					}
					else
					{
						cur_parent_parent->_right = cur;
						cur->_parent = cur_parent_parent;
					}
				}
				else
				{
					_root = cur;
					cur->_parent = nullptr;
				}

				cur_parent->_bf = cur->_bf = 0;
			}

			//左右双旋
			void RotateLR(Node* cur_parent)
			{
				Node* cur = cur_parent->_left;
				Node* cur_right = cur->_right;

				int bf = cur_right->_bf;

				//先对cur进行一个左单旋
				RotateL(cur);

				//再对cur_parent进行一个右单旋
				RotateR(cur_parent);

				//旋转完成后,要更新平衡因子
				if (bf == -1)
				{
					cur->_bf = 0;
					cur_parent->_bf = 1;
					cur_right->_bf = 0;
				}
				else if (bf == 1)
				{
					cur->_bf = -1;
					cur_parent->_bf = 0;
					cur_right->_bf = 0;
				}
				else if (bf == 0)//特殊情况
				{
					cur->_bf = 0;
					cur_parent->_bf = 0;
					cur_right->_bf = 0;
				}
			}

			//右左双旋
			void RotateRL(Node* cur_parent)
			{
				Node* cur = cur_parent->_right;
				Node* cur_left = cur->_left;

				int bf = cur_left->_bf;

				//先对cur进行一个右单旋
				RotateR(cur);

				//再对cur_parent进行一个左单旋
				RotateL(cur_parent);

				//更新平衡因子
				if (bf == -1)
				{
					cur->_bf = 1;
					cur_parent->_bf = 0;
					cur_left->_bf = 0;
				}
				else if (bf == 1)
				{
					cur->_bf = 0;
					cur_parent->_bf = -1;
					cur_left->_bf = 0;
				}
				else if (bf == 0)
				{
					cur->_bf = 0;
					cur_parent->_bf = 0;
					cur_left->_bf = 0;
				}
			}

			//中序遍历的子函数
			void _InOrder(Node* cur)
			{
				if (cur == nullptr)
					return;

				_InOrder(cur->_left);
				cout << cur->_data << " ";
				_InOrder(cur->_right);
			}

			//判断是否平衡的子函数
			bool _JudgeBanlance(Node* cur)
			{
				if (cur == nullptr)
					return true;

				int left_height = height(cur->_left);//求cur左子树的高度
				int right_height = height(cur->_right);//求cur右子树的高度

				return abs(left_height - right_height) < 2
					&& _JudgeBanlance(cur->_left)
					&& _JudgeBanlance(cur->_right);
			}

	private:
		Node* _root;
	};

	void Test1()
	{
		AVLTree<int> root;
		//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

		for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
		{
			root.insert(arr[i]);
		}
		root.InOrder();
		cout << root.JudgeBanlance() << endl;
	}

	void Test2()
	{
		srand(time(0));
		AVLTree<int> root;
		const int n = 100;
		for (int i = 0; i < n; i++)
		{
			root.insert(rand());
		}
		root.InOrder();
		cout << root.JudgeBanlance() << endl;
	}
}

Test.c

cpp 复制代码
#include"AVLTree.h"

int main()
{
	blog_AVLTree::Test1();
	//blog_AVLTree::Test2();
	return 0;
}
相关推荐
XH华2 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_2 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子2 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡3 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin3 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码3 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7243 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活3 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学4 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习