当前位置:   article > 正文

数据结构——AVL树_avl树树的高度

avl树树的高度

数据结构——AVL

前言

这是BST结构的升级版本,因为在某些情况下,BST结构的时间复杂度会退化成O(n),所以才有了这个完全平衡的AVL,会通过平衡因子维护二叉搜索树的平衡,保证其查找时的时间复杂度为O(log2(n)),但是也有缺点。

又因为再操作树型结构的时候,只有我们在使用插入或者删除方法时,才有可能会破坏树的结构,所以我们这里只记录,插入和删除的代码思想,应为AVL也是一颗树形结构,所以其他的对数的操作,同样适用。

一、AVL的性质

1.左右子树高度差(平衡因子)的绝对值不超过1
(1)高度差——平衡因子
每个节点都有自己的平衡因子,当平衡因子大于1时,我们就需要进行调整,使其小于等于1
(2) 这里,一般我们默认平衡因子的大小是右树减左树
例如:右数高度为2,左树高度为1,那么平衡因子就是 ‘1’ 反之则是 ‘-1’

2.每个节点左右子树都必须是AVL树
即不管从哪个节点出发,它的左右子树走必须是完全平衡的,该节点的平衡因子的绝对值不大于 ‘1’

先放一张建立的好的AVL树
在这里插入图片描述AVL利用自身特性将整棵树的高度控制在了log2(n),它的查询时间复杂度也是log2(n),但是还是有一些缺点,后面会提到。

二、AVL树的建立

1.节点的设置

因为我们是基于BST树的改进,所以节点的设置也大致相同,我们只需要加入维持AVL树平衡的平衡因子就好

template<class Type>
class AVLNode
{
	template<typename Type>
	friend class AVLTree;
public:
	AVLNode(Type val = Type(), int Bf = int(), AVLNode<Type>* leftchild = nullptr,AVLNode<Type>* rightchild = nullptr)
		:_LeftChild(leftchild), _RightChild(rightchild), _Val(val), _bf(Bf)
	{}
	~AVLNode()
	{}
private:
	AVLNode* _LeftChild;
	AVLNode* _RightChild;
	Type _Val;
	int _bf; //平衡因子
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

以上就是AVL树节点的简单设置。

2.树的初始化

上面是节点的初始化,我们光有节点也不行呀,也需要一颗树干,那么下面就是我们这颗树的初始化

template<class Type>
void AVLTree<Type>::_Insert(AVLNode<Type>*& root, const Type val)
{
	stack<AVLNode<Type>*> pr_st;
	AVLNode<Type>* p = root;
	AVLNode<Type>* pr = nullptr;
	while (p != nullptr)
	{
		pr = p;
		pr_st.push(pr);
		if (val > p->_Val)
			p = p->_RightChild;
		else
			p = p->_LeftChild;

	}
	p = new AVLNode<Type>(val);
	if (pr == nullptr)     //插入根节点
	{
		root = p;
		return;
	}

	if (val > pr->_Val)         //插入叶子节点
		pr->_RightChild = p;
	else
		pr->_LeftChild = p;
	 
	/*-----------------------------------------------------*/
	while(!pr_st.empty())       //调整平衡
	{
		pr = pr_st.top();       //开始回退,依次调整平衡因子
		pr_st.pop();

		if (p == pr->_LeftChild)     //插入的如果是左数,那么平衡因子-1,反之+1
			pr->_bf--;
		else
			pr->_bf++;
									
									//判断父节点是否平衡这里会出现三种情况
		if (pr->_bf == 0)			//情况一,插入节点后,平衡因子变为 '0'
			break;           //此时直接退出,因为原本高度不同,新插入节点刚好在矮的一边插入,并没有增加树的高度,所以不需要继续调整。
		else if (pr->_bf == 1 || pr->_bf == -1)     //情况二,插入节点后,平衡因子变为 '-1' 或 '1'
		{
			p = pr;          //此时是平衡的,所以我们需要向上回溯,因为此时树高度增加,需要回溯确认路径中每个节点是否出现不平衡
		}
		else if (pr->_bf == 2 || pr->_bf == -2)     //情况二,插入节点后,平衡因子变为 '-2' 或 '2'
		{                                     //直接就是不平衡,进行调整,调整完继续向上回溯
			if (pr->_bf < 0)
			{
				if (p->_bf < 0)
				{
					//右单旋
					_RotateR(pr);
				}
				else
				{
					//先左旋,再右旋
					_RotateLR(pr);
					pr->bf = 0;
				}
			}
			if(pr->_bf > 0)
			{
				if (p->_bf > 0)
				{
					//右单旋
					_RotateL(pr);
				}
				else
				{
					//先右旋,再左旋
					_RotateRL(pr);
					pr->bf = 0;
				}
			}
			break;        //因为调整完高度变回原来的高度,原来是平衡的,所以直接退出就好           
		}
	}
	
	if(!pr_st.empty())
	{
		AVLNode<Type>* temp = pr_st.top();
		if (temp->_Val > pr->_Val)     //再旋转时,引用传参,所以pr现在已经是旋转后的父节点,只要确定连接是连接pr父节点的左子树还是右子树就好
			temp->_LeftChild = pr;             
		else
			temp->_RightChild = pr;
	}
	else
	{
		root = pr;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

(1)寻找插入位置
看完代码之后,上面的那个问题自然就有了答案,如果我们使用递归,我们就很难执行下面的回溯,来调整路径上的各个节点,所以我们选择非递归的方式,进行插入位置的寻找,再寻找的同时,我们将会设置两个节点,一个父节点,一个子节点,来进行路径的保存,和未来进行回溯。

根节点插入那里要提一下, 因为是引用传参,我们直接将p节点覆盖就行,它会真实影响到树结构,所以不会存在覆盖后没有修改根节点的情况。这也是函数再定义时的一个巧妙的地方。

接下来通过插入的值来判断,插入的是左子树还是右子树,然后将 p节点进行插入

(2)调整平衡:
我们需要回溯,所以这里使用了循环和栈的配合,从我们建立的父节点储存栈pr_st中拿出我们第一个父节点,根据父节点的左子树还是右子树的插入进行平衡因子的调整,这里就会出现三种情况

情况一:情况一,插入节点后,平衡因子变为 '0’
这种情况,一般都是在较矮的一边树进行插入,并没有打破原本树的平衡,因为树的高度并没有发生变化,所以我们不需要进行调整,直接break,执行下次插入。

情况二,插入节点后,平衡因子变为 ‘-1’ 或 '1’
这种情况下,说明我们在原本平衡的节点进行插入,虽然并没有破坏这个节点的平衡,但是树的高度增加了,会影响到路径中其他父节点的平衡,这时我们需要回退,向上回溯,来寻找是否有其他父节点因为插入而导致不平衡

情况三,插入节点后,平衡因子变为 ‘-2’ 或 '2’
很明显,父节点不平衡,我们需要对其进行调整,那么我们刚才提到的,四种旋转就会出现(下方会有四种旋转的图码解释),再旋转过后,树的高度回到原来的高度,我们可以直接退出,不需要继续回溯。

(3)连接节点:
在旋转的时候已经将原本的父节点旋转成了子节点,因为是引用传参,所以旋转操作结束后,父节点依旧是旋转后的父节点,我们只需要确定父亲节应该连接它自己父节点的左子树还是右子树。

1.左单旋

在这里插入图片描述

从上面这张图中我们可以看到,a节点的右子树高度为2,左子树的高度为0,此时a节点的平衡因子就是2,就打破了AVL的性质,那么我们就需要调整平衡,这个调整平衡的方式就叫做左旋转。

在这里插入图片描述

旋转后的情况,如图所示,是不是很简单!!是不是很easy!!没错,确实简单-。-四种情况都是需要记忆的,所以这里我也不知道该记录什么,emmm,记下来什么情况该怎么旋转就好!!!

下面我们开始代码实现这个操作,其实很简单!

void AVLTree<Type>::_RotateL(AVLNode<Type>*& p)
{
	AVLNode<Type>* pr = p;           
	p = p->_RightChild;           //传进来的父节点,它追到自己的子节点
	pr->_RightChild = p->_LeftChild;        //p可能有左子树,所以需要将子树的左树挂到父节点的右树上
	p->_LeftChild = pr;          //旋转上来的新的父节点的左树指向原来的父节点完成旋转

	/*旋转完成后要调节平衡因子*/
	p->_bf = pr->_bf = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.右单旋

因为是对称操作,所以只有图和代码(哈哈哈哈哈哈哈)
在这里插入图片描述在这里插入图片描述

template<typename Type>
void AVLTree<Type>::_RotateR(AVLNode<Type>*& p)
{
	AVLNode<Type>* pr = p;
	p = p->_LeftChild;           //传进来的父节点,它追到自己的子节点
	pr->_LeftChild = p->RightChild;        //p可能有右子树,所以需要将子树的右树挂到父节点的左树上
	p->_RightChild = pr;          //旋转上来的新的父节点的右树指向原来的父节点完成旋转

	/*旋转完成后要调节平衡因子*/
	p->_bf = pr->_bf = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.先左旋再右旋

在这里插入图片描述

template<typename Type>
void AVLTree<Type>::_RotateLR(AVLNode<Type>*& p)
{
	AVLNode<Type>* ppr = p;     //根据图示,先将每一个位置都确定好
	AVLNode<Type>* pr = p->_LeftChild;   //因为要操纵三个节点,所以这次都要设置
	p = pr->_RightChild;   //p继续追到旋转后的父节点

	/*—————先左旋—————*/
	pr->_RightChild = p->_LeftChild;
	p->_LeftChild = pr;

	/*调节平衡因子*/
	if (p->_bf <= 0)
		pr->_bf = 0;
	else
		pr->_bf = -1;

	/*—————再右旋—————*/
	ppr->_LeftChild = p->_RightChild;
	p->_RightChild = ppr;

	/*调节平衡因子*/
	if (p->_bf >= 0)
		ppr->_bf = 0;
	else
		ppr->_bf = 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

这里旋转估计没什么大问题,主要是可能会不懂为什么平衡因子要这么调,其实也完全可以用上面写过的旋转函数,但是因为是初学,所以还是该写就写,不要省。多练练没坏处。

那么主要来说为什么平衡因子是这么调整的
首先我们需要知道一点!
只要进行双旋,p,pr.,ppr它们三个,必定是三同,还有就是如果有节点出现双旋转,那么旋转过后,该节点平衡因子一定为 0

三同:
例如先左旋再右旋,那么pr的左树ppr的右树,和**p的max(左树,右树)**一定是相同的高度,不然,没有进行到双旋转,只要其中有一个不一样,肯定在之前就已经进行了单旋,或者本来就是平衡。不可能出现双旋转,结论就来了,如果p->bf为0,pr->bf,ppr->bf 都为 0,那么,如果 p->bf != 0,那么pr一定有左树,ppr一定有右树,在这个前提下,如果pr->bf = 0,说明p有的是左树,那么ppr->bf = 1,反之则是pr->bf = -1.,ppr->vbf = 0.

在这里插入图片描述

4.先右旋再左旋

在这里插入图片描述

template<typename Type>
void AVLTree<Type>::_RotateRL(AVLNode<Type>*& p)
{
	AVLNode<Type>* ppr = p;     //根据图示,先将每一个位置都确定好
	AVLNode<Type>* pr = p->_RightChild;   //因为要操纵三个节点,所以这次都要设置
	p = pr->_LeftChild;   //p继续追到旋转后的父节点

	/*—————先右旋—————*/
	pr->_LeftChild = p->_RightChild;
	p->_RightChild = pr;

	/*调节平衡因子*/
	if (p->_bf >= 0)
		pr->_bf = 0;
	else
		pr->_bf = 1;

	/*—————再左旋—————*/
	ppr->_RightChild = p->_LeftChild;
	p->_LeftChild = ppr;

	/*调节平衡因子*/
	if (p->_bf <= 0)
		ppr->_bf = 0;
	else
		ppr->_bf = -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

四、AVL的删除操作

void AVLTree<Type>::_Remove(AVLNode<Type>*& root, Type val)
{
	AVLNode<Type>* pr = nullptr;
	AVLNode<Type>* q = nullptr;
	AVLNode<Type>* p = root;
	stack<AVLNode<Type>*> pr_st;
	while (p != nullptr)
	{	
		if (p->_Val == val)    //找到了需要删除的节点
			break;

		pr = p;
		pr_st.push(pr);

		if (val > p->_Val)
			p = p->_RightChild;
		else
			p = p->_LeftChild;
	}

	if (p == nullptr)    //没有找到需要删除的节点;
		return;

	if (p->_LeftChild != nullptr && p->_RightChild != nullptr)       //在左树寻找最大值,替换需要删除的节点。
	{
		pr = p;
		pr_st.push(pr);

		q = p->_LeftChild;
		while (q->_RightChild != nullptr)    //寻找要替换的节点,此时我们需要删除的节点就替换成了替换节点,所以父节点需要追下去,记录路径
		{
			pr = q;
			pr_st.push(pr);

			q = q->_RightChild;
		}

		p->_Val = q->_Val;                    //进行替换
		p = q;
	}

	if (p->_LeftChild != nullptr)      //检查该节点拥有左树还是右树
		q = p->_LeftChild;             //拥有左树就将q节点追到左树,待续要将p节点删除,那么这个它的节点就需要记录,不然就会丢失
	else
		q = p->_RightChild;            

	//方法一
	if (pr == nullptr)
	{
		delete p;
		root = pr;
		return;
	}
	if (p == pr->_LeftChild)
		pr->_LeftChild = q;
	else
		pr->_RightChild = q;
	//方法二
	//bool need_trim = false;
	//if (pr == nullptr )             //删除的是最后剩下的根节点
	//{
	//	delete p;
	//	root = nullptr;        
	//	return;
	//}
	//else
	//{
	//	if (p == pr->_LeftChild)        //将被删除节点的子树和父节点进行连接
	//	{
	//		pr->_bf++;
	//		pr->_LeftChild = q;
	//	}
	//	else
	//	{
	//		pr->_bf--;
	//		pr->_RightChild = q;
	//	}
	//}
	
	/*调整平衡*/
	bool need_break = false;
	while (!pr_st.empty())
	{
		pr = pr_st.top();
		pr_st.pop();
		
		//方法一
		if (p->_Val > pr->_Val)
			pr->_bf--;
		else
			pr->_bf++;
		//方法二
		//if (need_trim)
		//{
		//	if (q == pr->_LeftChild)   //调节平衡因子
		//		pr->_bf++;			   //如果pr的左树等于q说明刚才删除的是左子树,才需要把左子树的子树连接,左子树高度减1,那么原本的平衡因子就加1.
		//	else
		//		pr->_bf--;            //同理
		//}
		//else
		//	need_trim = true;

		if (pr->_bf == -1 || pr->_bf == 1)   //说明删除之前是平衡的,树的高度没有发生变化,并不会打破平衡,所以不需要调整,直接break
			break;

		if (pr->_bf == 0)      //树高度减小,在本来平衡因子为-1或者1的情况下,减少了左子树会或右子树,所以需要向上回溯,确认是否需要调整
			q = pr;

		else                 //此时平衡因子为2或者-2.所以需要进行调整
		{
			if (pr->_bf > 0)       //q追到pr的较高子树,因为我们旋转的时候,需要旋转较高子树
				q = pr->_RightChild;
			else
				q = pr->_LeftChild;

			if (q->_bf == 0)
			{
				if (pr->_LeftChild == q)
				{
					_RotateR(pr);
					pr->_bf = 1;
					pr->_RightChild->_bf = -1;
				}
				else
				{
					_RotateL(pr);
					pr->_bf = -1;
					pr->_LeftChild->_bf = 1;
				}
				need_break = true;   //调整完毕,并且在这种情况下,树的高度没有发生变化,所以可以退出
			}
			else
			{
				if (pr->_bf < 0 && q->_bf < 0)       //  右单旋
					_RotateR(pr);
				else if (pr->_bf > 0 && q->_bf > 0)  //   左单旋 
					_RotateL(pr);
				else if (pr->_bf < 0 && q->_bf>0)  //    先左旋再右旋
					_RotateLR(pr);
				else if (pr->_bf > 0 && q->_bf < 0)  //   先右旋再左旋
					_RotateRL(pr);

				q = pr;   //向上回溯,因为经过以上旋转可能会出现树高度降低的情况,所以需要向上回溯
			}

			/*连接节点*/
			if (pr_st.empty())   
				root = pr;      //回溯到树根了,修改根节点。或者调整的节点就是根节点     
			else
			{
				AVLNode<Type>* ppr = pr_st.top();  
				if (pr->_Val < ppr->_Val)
					ppr->_LeftChild = pr;
				else
					ppr->_RightChild = pr;

				if (need_break)
					break;
			}
		}
	}
	/*删除节点*/
	delete p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164

被删除节点的性质:
在删除的时候,首先要确定的一点就是,删除节点的状态:

1.被删除节点最多有且只有一个子树
2.被删除节点没有子树

这里有一个问题,被删除节点可以拥有两个叶子节点吗?显然是不可以的,那根节点有两个子树,你敢直接删吗,那树不就没了。所以,我们需要替换,将有两个子树但是要被删除的节点,替换到没有子树或者只有一个子树的位置,在进行删除,那么这里就有一个巧妙的地方,因为AVL是完全平衡的二叉搜索树,所以它左子树的最右边是左子树最大值,右子树的最左边是右子树的最小值,那么我们就可以跟两个节点其中一个进行替换,这样就不会破坏它身为二叉搜索树的性质,只需要删除后进行调整,就可以达到我们的目的,所以将有两个子树和有一个子树我们归纳处理得到最多有且只有一个子树和没有子树的节点才可以进行删除这个状态特征。

被删除节点的子树如何连接:
说到这里前两步就完成了,那么假如被删除节点有左子树呢,所以需要一个新节点来保存这个左子树,然后将它挂到被删除节点的父节点的左子树或者右子树上面,挂到哪里肯定是看被删除节点是父节点的左子树还右子树,你总不能把它挂到父节点的另外一边叭,那不就破坏了它是一个平衡二叉树的性质。

特别坑的地方:
下面就来说,代码里面标注出来的坑爹的地方,很容易在写的时候,感觉逻辑通顺,但其实有潜在问题。
在这里插入图片描述
就用这张图来看,和右边代码看看,逻辑看似没有问题。但是这样就会出现一个非常巧合的情况,如图所示,假设要删除红色节点,红色节点刚好还没有子树,那么q就是一个空节点,空节点挂到红色节点的父节点,这样说下来确实没有问题,但是恰恰问题就在这里,那么你后来要怎么去判断,你删除的是左树还是右树呢?都是空节点,没有办法进行判断,这时超级超级超级容易走进的误区!!!!只有这种特殊情况的发生,才可以注意到,也有可能误打误撞碰到正确的位置,但是这样显然是不稳定的,那么如何处理呢?

代码中实现了两种方法,可以去代码处进行查看。

调整平衡:

这里主要说一下,一些判断的原因:

if (pr->_bf == -1 || pr->_bf == 1)   //说明删除之前是平衡的,树的高度没有发生变化,并不会打破平衡,所以不需要调整,直接break
	break;
  • 1
  • 2

为什么会出现 ’1‘或’-1‘这样的情况,说明删除之前,这个节点本来平衡因子是‘0’,你删除了它的两个子树其中之一,所以才会出现这样的情况,这样说下来,只删除了其中之一,所以树的高度没有发生变化,并没有对路径中的节点产生影响,所以我们不要旋转调整平衡,直接突出循环即可
———————————————————————————————————————————————————


if (pr->_bf == 0)      //树高度减小,在本来平衡因子为-1或者1的情况下,减少了左子树会或右子树,所以需要向上回溯,确认是否需要调整
	q = pr;
  • 1
  • 2
  • 3

这里平衡因子为‘ 0 ’说明原来它并不是‘0’,你在删除了一个子树之后,++或者–才得到的‘0’,这时就说明树的高度发生变化,那么可能对他自己没有影响,但是对路径上其他节点可能会产生影响,所以我们要向上回溯,维护平衡因子。
———————————————————————————————————————————————————

else                 //此时平衡因子为2或者-2.所以需要进行调整
		{
			if (pr->_bf > 0)       //q追到pr的较高子树,因为我们旋转的时候,需要旋转较高子树
				q = pr->_RightChild;
			else
				q = pr->_LeftChild;

			if (q->_bf == 0)
			{
				if (pr->_LeftChild == q)
				{
					_RotateR(pr);
					pr->_bf = 1;
					pr->_RightChild->_bf = -1;
				}
				else
				{
					_RotateL(pr);
					pr->_bf = -1;
					pr->_LeftChild->_bf = 1;
				}
				need_break = true;   //调整完毕,并且在这种情况下,树的高度没有发生变化,所以可以退出
			}
			else
			{
				if (pr->_bf < 0 && q->_bf < 0)       //  右单旋
					_RotateR(pr);
				else if (pr->_bf > 0 && q->_bf > 0)  //   左单旋 
					_RotateL(pr);
				else if (pr->_bf < 0 && q->_bf>0)  //    先左旋再右旋
					_RotateLR(pr);
				else if (pr->_bf > 0 && q->_bf < 0)  //   先右旋再左旋
					_RotateRL(pr);

				q = pr;   //向上回溯,因为经过以上旋转可能会出现树高度降低的情况,所以需要向上回溯
			}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

最后一种情况就是,平衡因子为‘2’或者‘’-2‘的情况了,那么我们需要对该节点进行调整,调整的方法就是我们上面所写的四个旋转。既然是旋转那么我们就要去寻转符合旋转的条件,先将q节点追到pr节点的较高的子树,为什么要去较高的子树,因为我们旋转都是因为高的一边太高,导致高度差超过 1 ,所以我们要把高的一边给它砍了。这样就低了,然后追过来看看是什么样子,我们才能选择合适的方法把它砍下来。

追过去后,就有会两种情况:
在这里插入图片描述
1.q->_bf == 0
这种情况下,当前情况,符合单旋转的情况,是 ’ / ’ 或者 ’ \ ‘ 的情况,那么我们根据q再pr的左子树还是右子树,进行相应的旋转以及平衡因子的维护,但是再这种情况下我们的高度并没有发生变化,所以不会影响路径上其他节点的调整,此时我们直接退出即可。
———————————————————————————————————————————————————
2.q->_bf != 0
以右子树为例,那么就会有两种情况:
pr->_bf > 0 && q->_bf > 0,这时,就是左单旋
pr->_bf > 0 && q->_bf < 0,这时,就是双旋转,先右旋再左旋
但是这时,树的高度会发生变化,所以我们需要向上回溯,进行路径上的节点调整。

五、完整源代码(可以拷贝下来方便查看)

#include<iostream>
#include<stack>
#include<vector>
using namespace std;

template<class Type>
class AVLNode
{
	template<typename Type>
	friend class AVLTree;
public:
	AVLNode(Type val = Type(), int Bf = int(), AVLNode<Type>* leftchild = nullptr,AVLNode<Type>* rightchild = nullptr)
		:_LeftChild(leftchild), _RightChild(rightchild), _Val(val), _bf(Bf)
	{}
	~AVLNode()
	{}
private:
	AVLNode* _LeftChild;
	AVLNode* _RightChild;
	Type _Val;
	int _bf; //平衡因子
};
template<class Type>
class AVLTree
{
public:
	AVLTree(AVLNode<Type>* rt = nullptr)
		:_root(rt)
	{}
public:
	void Insert(Type val)
	{
		_Insert(_root, val);
	}
	void Remove(Type val)
	{
		_Remove(_root, val);
	}
	void Inorder()
	{
		_Inorder(_root);
	}
protected:
	void _Insert(AVLNode<Type>*& root, Type val);
	void _Remove(AVLNode<Type>*& root, Type val);
	void _RotateL(AVLNode<Type>*& p);
	void _RotateRL(AVLNode<Type>*& p);
	void _RotateR(AVLNode<Type>*& p);
	void _RotateLR(AVLNode<Type>*& p);
	void _Inorder(const AVLNode<Type>* root);
private:
	AVLNode<Type>* _root;
};
template<typename Type>
void AVLTree<Type>::_RotateL(AVLNode<Type>*& p)
{
	AVLNode<Type>* pr = p;           
	p = p->_RightChild;           //传进来的父节点,它追到自己的子节点
	pr->_RightChild = p->_LeftChild;        //p可能有左子树,所以需要将子树的左树挂到父节点的右树上
	p->_LeftChild = pr;          //旋转上来的新的父节点的左树指向原来的父节点完成旋转

	/*旋转完成后要调节平衡因子*/
	p->_bf = pr->_bf = 0;
}
template<typename Type>
void AVLTree<Type>::_RotateR(AVLNode<Type>*& p)
{
	AVLNode<Type>* pr = p;
	p = p->_LeftChild;           //传进来的父节点,它追到自己的子节点
	pr->_LeftChild = p->_RightChild;        //p可能有右子树,所以需要将子树的右树挂到父节点的左树上
	p->_RightChild = pr;          //旋转上来的新的父节点的右树指向原来的父节点完成旋转

	/*旋转完成后要调节平衡因子*/
	p->_bf = pr->_bf = 0;
}
template<typename Type>
void AVLTree<Type>::_RotateLR(AVLNode<Type>*& p)
{
	AVLNode<Type>* ppr = p;     //根据图示,先将每一个位置都确定好
	AVLNode<Type>* pr = p->_LeftChild;   //因为要操纵三个节点,所以这次都要设置
	p = pr->_RightChild;   //p继续追到旋转后的父节点

	/*—————先左旋—————*/
	pr->_RightChild = p->_LeftChild;
	p->_LeftChild = pr;

	/*调节平衡因子*/
	if (p->_bf <= 0)
		pr->_bf = 0;
	else
		pr->_bf = -1;

	/*—————再右旋—————*/
	ppr->_LeftChild = p->_RightChild;
	p->_RightChild = ppr;

	/*调节平衡因子*/
	if (p->_bf >= 0)
		ppr->_bf = 0;
	else
		ppr->_bf = 1;
}
template<typename Type>
void AVLTree<Type>::_RotateRL(AVLNode<Type>*& p)
{
	AVLNode<Type>* ppr = p;     //根据图示,先将每一个位置都确定好
	AVLNode<Type>* pr = p->_RightChild;   //因为要操纵三个节点,所以这次都要设置
	p = pr->_LeftChild;   //p继续追到旋转后的父节点

	/*—————先右旋—————*/
	pr->_LeftChild = p->_RightChild;
	p->_RightChild = pr;

	/*调节平衡因子*/
	if (p->_bf >= 0)
		pr->_bf = 0;
	else
		pr->_bf = 1;

	/*—————再左旋—————*/
	ppr->_RightChild = p->_LeftChild;
	p->_LeftChild = ppr;

	/*调节平衡因子*/
	if (p->_bf <= 0)
		ppr->_bf = 0;
	else
		ppr->_bf = -1;
}
template<class Type>
void AVLTree<Type>::_Insert(AVLNode<Type>*& root, const Type val)
{
	stack<AVLNode<Type>*> pr_st;
	AVLNode<Type>* p = root;
	AVLNode<Type>* pr = nullptr;
	while (p != nullptr)
	{
		pr = p;
		pr_st.push(pr);
		if (val > p->_Val)
			p = p->_RightChild;
		else
			p = p->_LeftChild;

	}
	p = new AVLNode<Type>(val);
	if (pr == nullptr)     //插入根节点
	{
		root = p;
		return;
	}

	if (val > pr->_Val)         //插入叶子节点
		pr->_RightChild = p;
	else
		pr->_LeftChild = p;
	 
	/*-----------------------------------------------------*/
	while(!pr_st.empty())       //调整平衡
	{
		pr = pr_st.top();       //开始回退,依次调整平衡因子
		pr_st.pop();

		if (p == pr->_LeftChild)     //插入的如果是左数,那么平衡因子-1,反之+1
			pr->_bf--;
		else
			pr->_bf++;
									
									//判断父节点是否平衡这里会出现三种情况
		if (pr->_bf == 0)			//情况一,插入节点后,平衡因子变为 '0'
			break;           //此时直接退出,因为原本高度不同,新插入节点刚好在矮的一边插入,并没有增加树的高度,所以不需要继续调整。
		else if (pr->_bf == 1 || pr->_bf == -1)     //情况二,插入节点后,平衡因子变为 '-1' 或 '1'
		{
			p = pr;          //此时是平衡的,所以我们需要向上回溯,因为此时树高度增加,需要回溯确认路径中每个节点是否出现不平衡
		}
		else if (pr->_bf == 2 || pr->_bf == -2)     //情况二,插入节点后,平衡因子变为 '-2' 或 '2'
		{                                     //直接就是不平衡,进行调整,调整完继续向上回溯
			if (pr->_bf < 0)
			{
				if (p->_bf < 0)
				{
					//有单旋
					_RotateR(pr);
				}
				else
				{
					//先左旋,再右旋
					_RotateLR(pr);
					pr->_bf = 0;
				}
			}
			else
			{
				if (p->_bf > 0)
				{
					//左单旋
					_RotateL(pr);
				}
				else
				{
					//先右旋,再左旋
					_RotateRL(pr);
					pr->_bf = 0;
				}
			}
			break;        //因为调整完高度变回原来的高度,原来是平衡的,所以直接退出就好           
		}
	}
	
	if(!pr_st.empty())
	{
		AVLNode<Type>* temp = pr_st.top();
		if (temp->_Val > pr->_Val)     //再旋转时,引用传参,所以pr现在已经是旋转后的父节点,只要确定连接是连接pr父节点的左子树还是右子树就好
			temp->_LeftChild = pr;             
		else
			temp->_RightChild = pr;
	}
	else
	{
		root = pr;
	}
}
template<class Type>
void AVLTree<Type>::_Inorder(const AVLNode<Type>* root)
{
	if (root == NULL)
		return;
	_Inorder(root->_LeftChild);
	cout << root->_Val << " ";
	_Inorder(root->_RightChild);
}
template<class Type>
void AVLTree<Type>::_Remove(AVLNode<Type>*& root, Type val)
{
	AVLNode<Type>* pr = nullptr;
	AVLNode<Type>* q = nullptr;
	AVLNode<Type>* p = root;
	stack<AVLNode<Type>*> pr_st;
	while (p != nullptr)
	{	
		if (p->_Val == val)    //找到了需要删除的节点
			break;

		pr = p;
		pr_st.push(pr);

		if (val > p->_Val)
			p = p->_RightChild;
		else
			p = p->_LeftChild;
	}

	if (p == nullptr)    //没有找到需要删除的节点;
		return;

	if (p->_LeftChild != nullptr && p->_RightChild != nullptr)       //在左树寻找最大值,替换需要删除的节点。
	{
		pr = p;
		pr_st.push(pr);

		q = p->_LeftChild;
		while (q->_RightChild != nullptr)    //寻找要替换的节点,此时我们需要删除的节点就替换成了替换节点,所以父节点需要追下去,记录路径
		{
			pr = q;
			pr_st.push(pr);

			q = q->_RightChild;
		}

		p->_Val = q->_Val;                    //进行替换
		p = q;
	}

	if (p->_LeftChild != nullptr)      //检查该节点拥有左树还是右树
		q = p->_LeftChild;             //拥有左树就将q节点追到左树,待续要将p节点删除,那么这个它的节点就需要记录,不然就会丢失
	else
		q = p->_RightChild;            

	bool need_trim = false;
	if (pr == nullptr )             //删除的是最后剩下的根节点
	{
		delete p;
		root = nullptr;        
		return;
	}
	else
	{
		if (p == pr->_LeftChild)        //将被删除节点的子树和父节点进行连接
		{
			pr->_bf++;
			pr->_LeftChild = q;
		}
		else
		{
			pr->_bf--;
			pr->_RightChild = q;
		}
	}
	
	/*调整平衡*/
	bool need_break = false;
	while (!pr_st.empty())
	{
		pr = pr_st.top();
		pr_st.pop();
		
		if (need_trim)
		{
			if (q == pr->_LeftChild)   //调节平衡因子
				pr->_bf++;			   //如果pr的左树等于q说明刚才删除的是左子树,才需要把左子树的子树连接,左子树高度减1,那么原本的平衡因子就加1.
			else
				pr->_bf--;            //同理
		}
		else
			need_trim = true;

		if (pr->_bf == -1 || pr->_bf == 1)   //说明删除之前是平衡的,树的高度没有发生变化,并不会打破平衡,所以不需要调整,直接break
			break;

		if (pr->_bf == 0)      //树高度减小,在本来平衡因子为-1或者1的情况下,减少了左子树会或右子树,所以需要向上回溯,确认是否需要调整
			q = pr;

		else                 //此时平衡因子为2或者-2.所以需要进行调整
		{
			if (pr->_bf > 0)       //q追到pr的较高子树,因为我们旋转的时候,需要旋转较高子树
				q = pr->_RightChild;
			else
				q = pr->_LeftChild;

			if (q->_bf == 0)
			{
				if (pr->_LeftChild == q)
				{
					_RotateR(pr);      
					pr->_bf = 1;
					pr->_RightChild->_bf = -1;
				}
				else
				{
					_RotateL(pr);
					pr->_bf = -1;
					pr->_LeftChild->_bf = 1;
				}
				need_break = true;   //调整完毕,并且在这种情况下,树的高度没有发生变化,所以可以退出
			}
			else
			{
				if (pr->_bf < 0 && q->_bf < 0)       //  右单旋
					_RotateR(pr);
				else if (pr->_bf > 0 && q->_bf > 0)  //   左单旋 
					_RotateL(pr);
				else if (pr->_bf < 0 && q->_bf>0)  //    先左旋再右旋
					_RotateLR(pr);
				else if (pr->_bf > 0 && q->_bf < 0)  //   先右旋再左旋
					_RotateRL(pr);

				q = pr;   //向上回溯,因为经过以上旋转可能会出现树高度降低的情况,所以需要向上回溯
			}

			/*连接节点*/
			if (pr_st.empty())   
				root = pr;      //回溯到树根了,修改根节点。或者调整的节点就是根节点     
			else
			{
				AVLNode<Type>* ppr = pr_st.top();  
				if (pr->_Val < ppr->_Val)
					ppr->_LeftChild = pr;
				else
					ppr->_RightChild = pr;

				if (need_break)
					break;
			}
		}
	}
	/*删除节点*/
	delete p;
}
int main()
{
	vector<int> v = { 8,5,9,4,6,7 };
	//vector<int> v = {16, 3, 7, 11, 9, 26, 18, 15, 1, 20, 50, 19};  //这个例子用来学习和画图非常好用
	AVLTree<int> A;
	for (auto& e : v)
	{
		A.Insert(e);
	}
	A.Inorder();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/551240
推荐阅读
相关标签
  

闽ICP备14008679号