数据结构:AVLTree的插入和删除的实现

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》

文章目录

  • 前言
  • 一、AVLTree
  • 二、AVLTree的插入
    • 插入新增节点
    • 调整平衡因子
    • 旋转
      • 左单旋(新增节点位于较高右子树的右侧)
      • 右单旋(新增节点位于较高左子树的左侧)
      • 右左双旋(新增节点在较高右子树的左子树上)
      • 左右双旋(新增节点在较高左子树的右子树上)
  • 三、AVLTree的删除
    • 删除节点
    • 调节平衡因子
  • 四、检查AVLTree的正确性
  • 代码实现
  • 总结


前言

本篇博客作为AVL树的插入和删除的实现。如果代码实现有问题,还请大佬们指出。


一、AVLTree

二叉搜索树虽然可以缩短查找的效率,但如果数据有序或者接近有序,二叉搜索树将退化成单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两个俄罗斯的数学家G.M.Adelson-Velskil和E.M.Landis在1962年发明了一种解决上述问题的方法:当二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度。
也就是说,一颗AVL树或者空树是具有以下性质的二叉树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(平衡因子)的绝对值不超过1(-1 \ 0 \ 1)。[ 平衡因子 = 右子树高度 - 左子树高度 ]

如下图所示:
在这里插入图片描述

如果一颗搜索二叉树是高度平衡的,它就是AVL树,如果它有N个节点,其高度为logn,搜索的时间复杂度为logn。

其中AVL树节点的定义:

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T data = T())
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_bf(0)
	{}

	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	T _data;
	int _bf;
};

其中_parent指针为指向父节点的指针,保证在插入和删除时,可以通过该指针进行回溯寻找父节点。(当然也可以不用该指针,转而在插入和删除的过程中,使用栈保存路径来进行回溯)

二、AVLTree的插入

AVL树的插入可以分为两步:

  1. 依据二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

插入新增节点

第一点很好实现,用两个指针变量cur,parent。cur来与新增节点比较,如果cur大于新增节点,cur就去左子树,如果cur小于新增节点,cur就去右子树,直到cur走的nullptr,表示新增节点要插入的位置。parent作为cur的父节点。需要注意的是,如果树为空,要进行特殊处理。

		// 树为空
		if (_root == nullptr) 
		{
			_root = new Node(data);
			return true;
		}
		
		// 寻找新增节点要插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) 
		{
			parent = cur;
			if (cur->_data < data)
			{
				cur = cur->_right;
			}
			else if (cur->_data > data)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		
		// 插入新增节点
		cur = new Node(data);
		cur->_parent = parent;
		if (parent->_data > data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

调整平衡因子

第二点调整平衡因子就有些麻烦,因为对于插入节点的祖先节点而言,它们的平衡因子都可能改变甚至失衡。
如下图所示:
在这里插入图片描述

对于上图中新增节点的祖先节点的平衡因子都发生改变。那么我们要如何处理平衡因子呢?

(平衡因子 = 右子树高度 - 左子树高度)

  • 如果插入节点是父节点的左子树(左子树高度+1),那么父节点的平衡因子-1
  • 如果插入节点是父节点的右子树(右子树高度+1),那么父节点的平衡因子+1

经过上述调整,父节点的平衡因子就会分成三种情况 0(该子树高度不变),正负1(该子树的高度改变),正负2(该子树已经失衡)。
在这里插入图片描述
如上图所示,我们在s的左子树插入一个节点,s的平衡因子变为0,s作为r的右子树的高度不变,r的平衡因子不需要改变,所以当调整完,父节点的平衡因子为0,我们不需要向上回溯处理祖先节点的平衡因子。
在这里插入图片描述

如上图所示,我们在c的左子树插入一个节点,c的平衡因子变为-1,c作为b的右子树的高度改变了,b的平衡因子也需要跟着改变,b的平衡因子变为1… 直到某一祖先节点的平衡因子变为0(如在插入一节点在s的左子树,那么其祖先节点r的平衡因子就为0),正负2(在t节点的左子树插入一节点,那么其祖先s节点的平衡因子变为2),回溯到根节点(上图所示)才能停止。
在这里插入图片描述
如上图所示,我们在t的右子树插入一个节点,t的平衡因子变为1,t作为s的右子树的高度改变了,s的平衡因子变为2,此时s的平衡因子失衡。此时我们就要对s这一子树进行旋转。

		while (parent)
		{
			if (parent->_left == cur) // 新增节点是父节点的左子节点
			{
				parent->_bf--;
			}
			else if(parent->_right == cur) // 新增节点是父节点的左子节点
			{
				parent->_bf++;
			}

			if (parent->_bf == 0) // 调整平衡因子后,父节点的平衡因子为0
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1) // 调整平衡因子后,父节点的平衡因子为正负1
			{
				cur = parent;
				parent = cur->_parent;
			}
			else if(parent->_bf == 2 || parent->_bf == -2) // 调整平衡因子后,父节点的平衡因子为正负2
			{
				// 失衡处理
				break;
			}
		}

旋转

对于失衡节点的旋转,我们可以分为四种情况左单旋右单旋左右双旋右左双旋
对于下图s的平衡因子失衡,我们就可以对于左单旋,使其回复平衡。
在这里插入图片描述

左单旋(新增节点位于较高右子树的右侧)

如下图s节点而言,新增节点位于其较高右子树的右侧。我们就需要对s节点进行左单旋。

在这里插入图片描述
那么如何进行左单旋呢?
我们先将定义四个指针pparent(r节点,s节点父节点),parent(s节点),subR(t节点,也就是s节点的右子树的根节点),subRL(t节点的左子树的根节点,在这里为nullptr)。此时我们只需要使parent->_right = subRL,subR-> _left = parent,subRL-> _parent = parent,subR-> _parent = parent-> _parent, parent-> _parent = subR,pparent-> _left = subR,使subR作为新子数的根节点,parent作为t较低左子树的根节点,再连接父节点指针即可(要注意此时subRL有可能为空,当parent为根节点时,pparent为空),此时 t 和 s 的平衡因子为0。
在这里插入图片描述
这是对于这个s节点的左单旋,那有没有可以概括整个左单旋的模型呢?
在这里插入图片描述

在这个模型中,我们可能会疑惑为什么a,b,c的高度都为h呢?这是因为如果当b = c = h,a = h-1,那么此时
parent的平衡因子已经失衡,且无论我们怎么旋转都不能解决失衡问题,再如果b = h-1,c = a = h,此时如果新增节点在b上,parent的平衡因子不会改变,如果新增节点在c上,subR的平衡因子就会先失衡。所以a,b,c的高度都要相当。
这样我们依据左单旋的模型就可以清晰的写出左单旋的代码,但不要忘记我们的节点还有一个父指针,要连接父子指针。

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

		if (subRL != nullptr)
		{
			subRL->_parent = parent;
		}
		Node* pparent = parent->_parent;
		if (pparent == nullptr) // 处理根节点
		{
			_root = subR;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else if (pparent->_right == parent)
			{
				pparent->_right = subR;
			}
		}
		subR->_parent = pparent;
		parent->_parent = subR;

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

右单旋(新增节点位于较高左子树的左侧)

右单旋与左单旋类似,这里我们就直接上模型了。
在这里插入图片描述
如上图所示,我们只需要使parent-> _left = subLR,subL->_right = parent,subRL->_parent = parent,subL->_parent = parent->_parent,parent->_parent = subL再将parent原先的父节点与该子树新的根节点subL连接即可。

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

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

		if (subLR != nullptr)
		{
			subLR->_parent = parent;
		}

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

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

右左双旋(新增节点在较高右子树的左子树上)

这里我们先上用例,再上模型。
如下图,如果我们在i,k,m,o节点下新增一个节点,都会发生右左双旋。
在这里插入图片描述
我们新增节点为m节点的左子节点,m的平衡因子变为-1,n的平衡因子变为-1…p的平衡因子变为-1,h的平衡因子变为2。此时h的平衡因子失衡(新增节点位于h节点较高右子树中,新增节点又位于p节点的左子树中)。我们需要先对p节点右单旋,再对h节点左单旋。
在这里插入图片描述

这样我们就可以完成右左双旋的操作。如果我们将新增节点加在i节点的左子节点上,那么虽然该树依据需要进行右左双旋的操作,h 和 p节点的平衡因子会发生改变。

在这里插入图片描述

因此,我们不仅需要两个指针parent(指向h节点,左单旋),subR(指向p节点,右单旋),还需要一个变量记录l节点的平衡因子。当l的平衡因子为1时,h的平衡因子为-1,p的平衡因子为0。当l的平衡因子为-1时,h的平衡因子为0,p的平衡因子为1。

在这里插入图片描述

这样我们就可以清楚的依据右左双旋模型来实现代码,但不要忘记将新子树的根节点与旧父节点相连接。

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

		RotateR(subR);
		RotateL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
		}
	}

左右双旋(新增节点在较高左子树的右子树上)

左右双旋与右左双旋类似,这里就直接上模型了。
在这里插入图片描述
我们先对subL节点左单旋,再对parent节点右单旋,最后依据subLR节点的平衡因子来修改parent节点,subL节点的平衡因子

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

		RotateL(subL);
		RotateR(parent);

		if (bf == -1)
		{
			parent->_bf = 1;
		}
		else if(bf == 1)
		{
			subL->_bf = -1;
		}
	}

至此四种旋转就结束了,那么什么时候使用那种旋转呢?
在这里插入图片描述
依据上图判断parent节点与cur节点的平衡因子即可知晓使用那种平衡。


				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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				
				// 旋转后,该子树就不需要再向上回溯
				break;

三、AVLTree的删除

AVRTree的删除与二叉搜索树类似,也可以分成三种情况来看(实际情况一与情况二在实现上可以合并)

  • 情况一:要删除节点为叶子节点,则直接删除,调节平衡因子
  • 情况二:要删除节点有一个子节点,将该节点的父节点原来指向该节点的指针,直接指向该节点的子节点,删除该节点,调节平衡因子
  • 情况三:要删除节点有两个子节点,找该节点在中序遍历中的直接前驱节点,将该节点与前驱节点直接替换,该前驱节点必定只有一个右子节点或没有子节点,按照情况二删除前驱节点,调整平衡因子。

删除节点

我们将情况一和情况而合并为同一种情况处理。这种情况删除节点很好处理,我们只需找到该节点让该节点的父节点原指向该节点的指针指向该节点的子节点即可。但情况三有一个问题,当我们找到该节点后,如何找该节点在中序遍历中的直接前驱节点?
如下图所示:该图中序遍历结果就是字母a到字母t的顺序
在这里插入图片描述
如果我们要删除h节点,那h节点在中序遍历中的直接前驱就是g节点,如果删除的是p节点,那么前驱节点就是o节点,如果删除d节点,前驱节点就是c节点。那么有什么发现吗?一个节点的“前驱节点”就是该节点左子树的最右节点,毕竟中序遍历是左 根 右,左子树的最后一个节点就是该节点的“前驱节点”。

		Node* cur = _root;    // 要删除的节点
		Node* prev = nullptr; // 要删除节点的父节点
		Node* sub = nullptr;  // 要删除节点的子节点
		int flag = 0; // flag = 1表示删除节点是右子节点   flag = -1表示删除节点是左节点
		while (cur != nullptr && cur->_data != data)
		{
			if (cur->_data > data)
			{
				cur = cur->_left;
			}
			else if (cur->_data < data)
			{
				cur = cur->_right;
			}
		}
		if (cur == nullptr)
		{
			return false;
		}
		prev = cur->_parent;


		// 处理有两个子节点
		if (cur->_left != nullptr && cur->_right != nullptr)
		{
			// 将cur 与 中序次序的直接前序替换
			sub = cur->_left;
			while (sub->_right != nullptr)
			{
				sub = sub->_right;
			}
			prev = sub->_parent;
			cur->_data = sub->_data;
			cur = sub;
			// prev->_right = sub->_left;
		}
		
		// 处理有单个子节点
		if (cur->_left != nullptr)
		{
			sub = cur->_left;
		}
		else
		{
			sub = cur->_right;
		}
		if (prev == nullptr) // 删除的是根节点
		{
			delete cur;
			cur = nullptr;

			_root = sub;
			if(sub != nullptr)
				sub->_parent = nullptr;
		}
		else
		{
			if (sub != nullptr)
			{
				sub->_parent = prev;
			}
			if (prev->_left == cur)
			{
				flag = -1;
				prev->_left = sub;
			}
			else
			{
				flag = 1;
				prev->_right = sub;
			}
			delete cur;
			cur = nullptr;
			
			// 调节平衡因子
		}

调节平衡因子

依据平衡因子 = 右子树高度 - 左子树高度

  • 如果删除的节点是父节点的左子节点,那么父节点的平衡因子+1
  • 如果删除的节点是父节点的右子节点,那么父节点的平衡因子-1

那么修改后,父节点的平衡因子会有三种情况。

  1. 父节点的平衡因子本身是0,修改后变成正负1,此时该子树高度不变,不需要向上回溯。
    在这里插入图片描述
    如上图删除o节点,对于以n为根节点的子树而言高度不变,不需要向上回溯调整平衡因子。
  2. 父节点的平衡因子本身不为0,删除较高子树的节点,修改后变成0,此时该子树高度发生改变,需要向上回溯调节祖先节点的平衡因子
    在这里插入图片描述
    如上图删除t节点,s节点的平衡因子变为0,对于以s节点为根节点的子树而言,其高度减少,需要向上回溯,p节点的平衡因子变为-1,对于以p节点为根节点的子树而言,其高度不变,不需要向上回溯。
  3. 父节点的平衡因子本身不为0,删除较低子树的节点,修改后平衡因子变成正负2,此时该子树失衡,需要旋转。我们现将parent(父节点)的较高子节点记录为sub。此时我们又可以分成三种情况。
  1. sub的平衡因子为0,此时我们只需要单旋处理即可,不需要向上调整平衡因子。
    在这里插入图片描述
    如果我们是连续删除e,g,f节点,那么b节点作为d节点中较高节点(左子节点)的平衡因子为0,需要右单旋来完成平衡。
    2.sub的平衡因子不为0,prev(父节点)的平衡因子与sub(父节点的子节点中较高子节点)平衡因子同号,只需要一个单旋来完成平衡,但该子树的高度发生改变,需要向上回溯,调节祖先节点的平衡因子。
    在这里插入图片描述
    如上图我们删除节点q,此时r节点的平衡因子为2,s节点的平衡因子为1,我们需要左单旋来完成平衡,但新的以s节点为根的子树高度变低了,我们需要向上回溯,调整p节点的平衡因子,此时p的平衡因子为-1。对于模型中sub的左子树部分,可能有人会疑惑为什么高度为h-1,而不是h?如果sub左子树部分高度为h,该模型不就是情况1的模型。
    3.sub的平衡因子不为0,prev的平衡因子与sub的平衡因子异号,此时需要执行双旋来完成平衡,且完成双旋后高度降低,需要向上回溯,调节祖先节点的平衡因子。
    在这里插入图片描述
    如上图我们连续删除a,c,e,g,t节点,此时h节点的平衡因子为2,p节点的平衡因子为-1,我们需要右左双旋来完成平衡,先对sub右旋,再对prev左旋,旋转后,此时以l为根节点的新子树高度减小,需要向上调整,但l节点恰好是整个树的根节点。不用向上调整。

至此删除节点后调节平衡因子的所以情况都以说完。

			while (prev != nullptr) // 调节平衡因子
			{
				if (flag == -1) // 如果子节点删除完成后,其父节点的左右子节点都为nullptr,此时直接判断perv->_left == cur则无法区分。
				{
					prev->_bf++; 
				}
				else if (flag == 1)
				{
					prev->_bf--;
				}

				if (prev->_bf == -1 || prev->_bf == 1)
				{
					break; // 该子树的高度不变
				}
				else if (prev->_bf != 0) // prev->_bf == 2 || prev->_bf == -2
				{
					// 使 sub 表示最高子树
					if (prev->_bf > 0)
					{
						sub = prev->_right;
					}
					else
					{
						sub = prev->_left;
					}

					if (sub->_bf == 0) // 该子树平衡后高度不变
					{
						if (prev->_left == sub)
						{
							RotateR(prev);
							sub->_bf = 1;
							prev->_bf = -1;
						}
						else
						{
							RotateL(prev);
							sub->_bf = -1;
							prev->_bf = 1;
						}
						break;
					}
					else if ((prev->_bf) * (sub->_bf) > 0) // 同号  
					{
						if (sub == prev->_right)
						{
							RotateL(prev);
						}
						else if (sub == prev->_left)
						{
							RotateR(prev);
						}
						prev = sub->_parent;
					}
					else if ((prev->_bf) * (sub->_bf) < 0) // 异号  
					{
						if (sub == prev->_right)
						{
							RotateRL(prev);
						}
						else if (sub == prev->_left)
						{
							RotateLR(prev);
						}
						sub = sub->_parent;
						prev = sub->_parent;
					}
				}
				else if (prev->_bf == 0)
				{
					sub = prev;
					prev = prev->_parent;
				}

				//  更新 flag 的值
				if (prev != nullptr)
				{
					if (prev->_left == sub)
					{
						flag = -1;
					}
					else if (prev->_right == sub)
					{
						flag = 1;
					}
				}
			}

四、检查AVLTree的正确性

验证AVLTree的正确分为两步:

  • 验证其是否是二叉搜索树,打印中序遍历结果,看结果序列是否是升序序列
  • 验证其是否是平衡数,每个子节点的高度差不大于1,节点的平衡因子是否正确。

这两步都非常简单,我们只需递归比较每个节点的左右子树的高度,看是否符合定义即可。

	bool isAVLTEree()
	{
		return _isAVLTree(_root);
	}
	
	bool _isAVLTree(Node* root)
	{
		if (root == nullptr)
			return true;
		
		int leftHeight = _Hight(root->_left);
		int rightHeight = _Hight(root->_right);

		if(rightHeight - leftHeight != root->_bf)
		{
			cout << root->_data << "平衡因子失衡" << endl;
		}
		return abs(leftHeight - rightHeight) >= 2 ? false : true;
	}

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

		return max(_Hight(root->_left), _Hight(root->_right)) + 1;
	}

代码实现

AVLTree.h文件存放AVLTree插入删除的实现。
test.cpp文件存放的是测试用例。

#pragma once
#include <iostream>
#include <assert.h>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <string>

using namespace std;


template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T data = T())
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_bf(0)
	{}

	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	T _data;
	int _bf;
};


template<class T>
class AVLTree
{
public:
	typedef AVLTreeNode<T> Node;

	AVLTree()
		:_root(nullptr)
	{}

	bool insert(const T data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_data < data)
			{
				cur = cur->_right;
			}
			else if (cur->_data > data)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		cur->_parent = parent;
		if (parent->_data > data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		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 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}

				break;
			}
		}

		return true;
	}

	bool Erase(const T data)
	{
		Node* cur = _root;
		Node* prev = nullptr;
		Node* sub = nullptr;
		int flag = 0; // flag = 1表示删除节点是右子节点   flag = -1表示删除节点是左节点
		while (cur != nullptr && cur->_data != data)
		{
			if (cur->_data > data)
			{
				cur = cur->_left;
			}
			else if (cur->_data < data)
			{
				cur = cur->_right;
			}
		}
		if (cur == nullptr)
		{
			return false;
		}
		prev = cur->_parent;


		// 处理有两个子节点
		if (cur->_left != nullptr && cur->_right != nullptr)
		{
			// 将cur 与 中序次序的直接前序替换
			sub = cur->_left;
			while (sub->_right != nullptr)
			{
				sub = sub->_right;
			}
			prev = sub->_parent;
			cur->_data = sub->_data;
			cur = sub;
			// prev->_right = sub->_left;
		}
		
		// 处理有单个子节点
		if (cur->_left != nullptr)
		{
			sub = cur->_left;
		}
		else
		{
			sub = cur->_right;
		}
		if (prev == nullptr) // 删除的是根节点
		{
			delete cur;
			cur = nullptr;

			_root = sub;
			if(sub != nullptr)
				sub->_parent = nullptr;
		}
		else
		{
			if (sub != nullptr)
			{
				sub->_parent = prev;
			}
			if (prev->_left == cur)
			{
				flag = -1;
				prev->_left = sub;
			}
			else
			{
				flag = 1;
				prev->_right = sub;
			}

			delete cur;
			cur = nullptr;
			while (prev != nullptr) // 调节平衡因子
			{
				if (flag == -1) // 如果子节点删除完成后,其父节点的左右子节点都为nullptr,此时直接判断perv->_left == cur则无法区分。
				{
					prev->_bf++; 
				}
				else if (flag == 1)
				{
					prev->_bf--;
				}

				if (prev->_bf == -1 || prev->_bf == 1)
				{
					break; // 该子树的高度不变
				}
				else if (prev->_bf != 0) // prev->_bf == 2 || prev->_bf == -2
				{
					// 使 sub 表示最高子树
					if (prev->_bf > 0)
					{
						sub = prev->_right;
					}
					else
					{
						sub = prev->_left;
					}

					if (sub->_bf == 0) // 该子树平衡后高度不变
					{
						if (prev->_left == sub)
						{
							RotateR(prev);
							sub->_bf = 1;
							prev->_bf = -1;
						}
						else
						{
							RotateL(prev);
							sub->_bf = -1;
							prev->_bf = 1;
						}
						break;
					}
					else if ((prev->_bf) * (sub->_bf) > 0) // 同号  
					{
						if (sub == prev->_right)
						{
							RotateL(prev);
						}
						else if (sub == prev->_left)
						{
							RotateR(prev);
						}
						prev = sub->_parent;
					}
					else if ((prev->_bf) * (sub->_bf) < 0) // 异号  
					{
						if (sub == prev->_right)
						{
							RotateRL(prev);
						}
						else if (sub == prev->_left)
						{
							RotateLR(prev);
						}
						sub = sub->_parent;
						prev = sub->_parent;
					}
				}
				else if (prev->_bf == 0)
				{
					sub = prev;
					prev = prev->_parent;
				}

				//  更新 flag 的值
				if (prev != nullptr)
				{
					if (prev->_left == sub)
					{
						flag = -1;
					}
					else if (prev->_right == sub)
					{
						flag = 1;
					}
				}
			}
		}

		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	bool isAVLTEree()
	{
		return _isAVLTree(_root);
	}

	size_t Hight()
	{
		return _Hight(_root);
	}
private:
	bool _isAVLTree(Node* root)
	{
		if (root == nullptr)
			return true;
		
		int leftHeight = _Hight(root->_left);
		int rightHeight = _Hight(root->_right);

		if(rightHeight - leftHeight != root->_bf)
		{
			cout << root->_data << "平衡因子失衡" << endl;
		}
		return abs(leftHeight - rightHeight) >= 2 ? false : true;
	}

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

		return max(_Hight(root->_left), _Hight(root->_right)) + 1;
	}

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

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

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

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

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

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

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

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

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

		if (subLR != nullptr)
		{
			subLR->_parent = parent;
		}

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

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

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

		RotateL(subL);
		RotateR(parent);

		if (bf == -1)
		{
			parent->_bf = 1;
		}
		else if(bf == 1)
		{
			subL->_bf = -1;
		}
	}

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

		RotateR(subR);
		RotateL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
		}
	}

	Node* _root;
};

// test.cpp 文件
#include "AVLTree.h"

void test1()
{
	//vector<int> nums({ 16,3,7,11,9,26,18,14,15 });
	vector<int> nums({ 4,2,6,1,3,5,15,7,16,14 });
	AVLTree<int> tree;

	for (auto val : nums)
	{
		tree.insert(val);
	}

	tree.InOrder();
	cout << endl;
	cout << tree.isAVLTEree() << endl;
	cout << tree.Hight() << endl;
}

void test2()
{
	const int N = 1000;
	vector<int> v(N);
	srand(time(0));

	for (int i = 0; i < N; i++)
	{
		v[i] = rand();
		cout << v[i] << endl;
	}

	AVLTree<int> tree;
	for (auto val : v)
	{
		tree.insert(val);
		//cout << "insert:" << val << "->" << tree.isAVLTEree() << endl;
	}

	cout << tree.Hight() << endl;
	cout << tree.isAVLTEree() << endl;

	
	int i = 0;
	for (auto val : v)
	{
		tree.Erase(val);
		cout << "Erase:" << val << "->" << i++ << endl;
	}
	cout << tree.isAVLTEree() << endl;

}


void test3()
{
	string str("abcdefghijklmnopqrst");
	AVLTree<char> t;

	for (auto data : str)
	{
		t.insert(data);
	}

	for (int i = 0; i < str.size(); i++)
	{
		t.Erase(str[i]);
	}
	t.InOrder();

	cout << t.isAVLTEree() << endl;
}


void test4()
{
	AVLTree<int> t;
	t.insert(1);
	t.insert(3);
	t.insert(2);
	t.insert(4);

	for (int i = 0; i < 5; i++)
	{
		t.Erase(i);
	}
	cout << t.isAVLTEree() << endl;
}
int main()
{
	test2();

	return 0;
}

2000个数据测试结果。
在这里插入图片描述

20000个数据结果
在这里插入图片描述
200000个测试结果

在这里插入图片描述
2000000个数据测试结果。

在这里插入图片描述


总结

以上就是我对于AVLTree插入和删除的理解。感谢支持!!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【2011年数据结构真题】

41题 41题解答&#xff1a; &#xff08;1&#xff09;图 G 的邻接矩阵 A 如下所示&#xff1a; 由题意得&#xff0c;A为上三角矩阵&#xff0c;在上三角矩阵A[6][6]中&#xff0c;第1行至第5行主对角线上方的元素个数分别为5, 4, 3, 2, 1 用 “ 平移” 的思想&#xff0c;…

ENVI IDL:如何将txt文本文件转化为GeoTIFF文件?

01 前言 此处的文本文件形式如下&#xff1a; 里面包含了众多点位信息&#xff08;不是站点数据&#xff09;&#xff0c;我们需要依据上述点的经纬度信息放到对应位置的像素点位置&#xff0c;放置完后如下&#xff1a; 可以发现&#xff0c;还存在部分缺失值&#xff0c;我们…

Qt实现TCP调试助手 - 简述如何在Qt中实现TCP多并发

简介 软件开发中&#xff0c;可能经常会用到TCP调试工具。本人使用QT开发了一款TCP调试工具&#xff0c;方便大家使用。本文章主要介绍下&#xff0c;该工具的功能&#xff0c;以及如何在Qt中实现TCP服务器的并发。 界面展示 安装界面 桌面图标。安装后会生成桌面图标&#…

【操作系统】1.1 操作系统的基础概念、功能和目标以及特性

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

手机开机入网流程 KPI接通率和掉线率

今天我们来学习手机开机入网流程是怎么样的。以及RRC连接和重建流程(和博主之前讲TCP三次握手&#xff0c;四次挥手原理很相似)是什么样的&#xff0c;还有天线的KPI指标都包括什么&#xff0c;是不是很期待啊~ 目录 手机开机入网流程 ATTACH/RRC连接建立过程 KPI接通率和掉…

MongoDB基础知识~

引入MongoDB&#xff1a; 在面对高并发&#xff0c;高效率存储和访问&#xff0c;高扩展性和高可用性等的需求下&#xff0c;我们之前所学习过的关系型数据库(MySql,sql server…)显得有点力不从心&#xff0c;而这些需求在我们的生活中也是随处可见的&#xff0c;例如在社交中…

node插件express(路由)的插件使用(二)——cookie 和 session的基本使用区别

文章目录 前言一、express 框架中的 cookie0、cookie的介绍和作用1. 设置cookie2.删除cookie3.获取cookie&#xff08;1&#xff09;安装cookie-parser&#xff08;2&#xff09;导入cookie-parser&#xff08;3&#xff09;注册中间件&#xff08;4&#xff09;获取cookie&…

力扣刷题篇之栈与队列2(待修改)

系列文章目录 目录 系列文章目录 前言 一、最小/大栈 二、字符串去重问题 三、栈与括号匹配 总结 前言 本系列是个人力扣刷题汇总&#xff0c;本文是栈与队列。刷题顺序按照[力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 - 力扣&#xff08;LeetCode&#xff09…

Nginx:Windows详细安装部署教程

一、Nginx简介 Nginx (engine x) 是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的。 它也是一种轻量级的Web服务器…

解决springboot接受buffer文件为null(从picgo上传buffer看springmvc处理过程)

1. 前言&#xff1a; picgo插件的简单开发 上篇文章我们简单写了picgo上传插件&#xff0c;但是当我们测试的时候&#xff0c;发现问题了&#xff0c;后端MultipartFile file接受到的文件为null。 2. 排查问题&#xff1a; 参考的文档 picgo api列表关于multipart form-data中…

C语言从入门到精通之【概述】

#include指令和头文件 例如#include <stdio.h>&#xff0c;我们经常看到C文件最上面会有类似这样的语句&#xff0c;它的作用相当于把stdio.h文件中的所有内容都输入该行所在的位置。实际上&#xff0c;这是一种“拷贝-粘贴”的操作。 #include这行代码是一条C预处理器…

LeetCode200.岛屿数量

看完题目我还感觉这道题目有点难&#xff0c;没想到20分钟不到就完全靠自己给写出来了。我就是按照自己的想法来&#xff0c;我用一个等大的visit数组来表示grid数组中的这个元素是否被访问过&#xff08;是否已经被判断了是不是岛屿&#xff09;。 先用一个大的循环对grid数组…

按键精灵中的字符串常用的场景

在使用按键精灵编写脚本时&#xff0c;与字符串有关的场景有以下几种&#xff1a; 1. 用时间字符串记录脚本使用截止使用时间 Dim localTime "2023-11-12 00:15:14" Dim networkTime GetNetworkTime() TracePrint networkTime If networkTime > localTime The…

KT6368A蓝牙芯片的出现部分芯片距离短换芯片就好是什么问题呢

一、简介 KT6368A蓝牙芯片的出现部分芯片距离短&#xff0c;换一个芯片距离就好了&#xff0c;是什么问题呢&#xff1f;生产2K的样子 详细说明 按照我们出货客户的跟踪情况&#xff0c;这种问题&#xff0c;可能性极低因为芯片本身的不良率&#xff0c;目前是控制在千分之三…

无需公网IP,贝锐花生壳内网穿透远程访问NAS

群晖DSM 7.0及以上版本 1.1 安装运行花生壳套件 &#xff08;1&#xff09;通过浏览器输入群晖NAS的内网地址&#xff0c;登录进去后&#xff0c;点击【套件中心】&#xff0c;搜索【花生壳】&#xff0c;并点击【安装套件】&#xff1b; &#xff08;2&#xff09; 勾选我接…

【C++】手写堆

手写堆&#xff08;小顶堆&#xff09; 堆使用数组存储&#xff0c;下标从1开始&#xff08;下标从0开始也可以&#xff09;。 下标为u的节点&#xff1a; 左子节点下标为&#xff1a;2 * u&#xff08;下标从0开始&#xff0c;左子节点则为2 * i 1&#xff09;右子节点下标…

No184.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

NGINX三种虚拟主机的配置

基于IP的配置 首先在原本基础上增加两个IP地址 [rootlocalhost conf.d]# nmcli connection modify ens33 ipv4.addresses 192.168.38.140 [rootlocalhost conf.d]# nmcli connection modify ens33 ipv4.addresses 192.168.38.150 [rootlocalhost conf.d]# nmcli connection u…

绝了!现在制作电子期刊这么快而有效了?

​随着科技的进步&#xff0c;制作电子期刊已经变得越来越简单和高效。现在&#xff0c;您只需要一台电脑或手机&#xff0c;就可以快速制作出精美的电子期刊&#xff0c;而且还能有效地吸引读者的注意力。 但是如何快而有效的制作电子期刊呢&#xff1f; 1.首先打开FLBOOK在线…

网络安全之认识托管威胁检测与响应(MDR)

随着数字化转型加速&#xff0c;企业的IT环境日益复杂&#xff0c;面临的网络安全威胁也在不断增加。传统的防御措施已经无法有效应对新型威胁&#xff0c;而且很多企业缺乏专业的网络安全团队和技术手段&#xff0c;导致大量的安全事件未能及时被发现和处理。 在这种背景下&a…