AVL树介绍与实现

走好选择的路,别选择好走的路,你才能拥有真正的自己。


在之前的二叉搜索树当中,虽然可以提高我们查找数据的时间效率,但如果插入二叉搜索树的数据是有序或接近有序的,此时二叉搜索树会退化为单支树,此时就相当于是一个单链表,在单链表当中查找数据,效率是很低下的,时间复杂度会退化到O(n)。因此下面要讲的AVL树就是二叉搜索树,但与传统的二叉搜索树不同,它们不会退化到O(n),且增删查改的时间复杂度为O(logN)。

AVL树

AVL树概念

两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:
AVL树就是为了避免树退化为单支树而创建的树,规定在向二叉搜索树中插入新节点时,如果能保证每个节点的左右子树高度之差的绝对值不超1(需要对树中的节点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1、它的左右子树都是AVL树。
2、左右子树高度之差(简称平衡因子)的绝对值不超过1。

在这里插入图片描述

AVL树结点的定义

我们这里直接实现KV模型的AVL树,这里我就直接使用pair,为了方便后续的操作,这里将AVL树中的结点定义为三叉链的结构,并在每个结点当中引入平衡因子(右子树高度-左子树高度)。除此之外,还需要一个构造函数,当new AVL树对象时,此时new会先申请空间,然后再去调用构造函数,将每个结点的指针的指向全部置空,否则后期容器出错,且新构造结点的左右子树均为空树,于是将新构造结点的平衡因子初始设置为0即可。

template<class K,class V>
struct AVLTreeNode	//三叉链
{
	AVLTreeNode* m_left;  
	AVLTreeNode* m_right;
	AVLTreeNode* m_parent;

	pair<K, V>m_kv; //K V 模型
	int m_bf;	//平衡因子


	AVLTreeNode(const pair<K, V>&kv)
		: m_left(nullptr), m_right(nullptr), m_parent(nullptr)
		, m_kv(kv), m_bf(0)
	{}
};

注意: 这里结点的结构不一定要定义成三叉链且带平衡因子,这里只是为了实现简单方便加上的,且我们这里的平衡因子计算是右子树高度-左子树高度,不意味着反过来就不行。

AVL树插入

AVL树的插入与二叉搜索树插入规则相同。具体可以看之前的二叉搜索树的博客。
1、待插入结点的key值比当前结点小就插入到该结点的左子树。
2、待插入结点的key值比当前结点大就插入到该结点的右子树。
3、待插入结点的key值与当前结点的key值相等就插入失败。

而AVL树与普通的二叉搜索树不同的是,每次插入一个结点进去,都需要从插入位置开始一直沿着父亲往上更新平衡因子。如下图所示,待插入的结点为5,此时插入进去之后要更新5开始一直更新平衡因子,最差要更新到根结点。
在这里插入图片描述
我们插入结点后更新的平衡因子的规则如下:

  • 新增结点在parent的左边,parent的平衡因子 - -
  • 新增结点在parent的右边,parent的平衡因子++

每更新完一个祖先结点就判断一下祖先结点的平衡因子是否出现问题。

parent更新后的平衡因子分析
0当更新完之后parent的平衡因子变为0,此时只有-1/1经过++或 - -操作后会变成0,说明新结点插入到了parent左右子树当中高度较矮的一棵子树,插入后使得parent左右子树的高度相等了,此操作并没有改变以parent为根结点的子树的高度,从而不会影响parent的父结点的平衡因子,因此无需继续往上更新平衡因子。 此时我们直接break跳出更新语句即可。
-1/1当更新完之后parent的平衡因子变为-1/1,此时只有0经过++或 - -操作后会变成-1/1,说明原来以parent为根结点的左右子树的高度是相等的,而你插入一个新的结点进来,影响了parent的平衡因子,进而影响到了parent的父结点的平衡因子,此时需要继续往上更新。
-2/2当更新完之后parent的平衡因子变为-2/2,此时只有-1/1经过++或 - -操作后会变成-2/2,说明新结点插入到了parent左右子树当中高度较高的一棵子树,使得parent左右子树高度差等于-2/2了,不满足AVL的性质了,为了让其继续满足,此时我们就需要旋转操作来让其平衡。

若cur为新增结点,此时cur的平衡因子为0,此时cur的父亲结点的平衡因子此时有3种情况:
parent的平衡因子更新平衡因子后,平衡因子一定是在-1/0/1这三种情况之中。因为新增结点cur最终会插入到parent的一个空树当中,在新增结点插入前,其父结点的状态有以下三种可能:

  • 父结点是一个左右子树均为空的叶子结点,其平衡因子是0,新增结点插入后其平衡因子更新为-1/1。
  • 父结点是一个左子树或右子树为空的结点,其平衡因子是-1/1,新增结点插入到其父结点的空子树当中,使得其父结点左右子树当中较矮的一棵子树增高了,新增结点后其平衡因子更新为0。

此时父结点的平衡因子一定不可能为-2或者2,如果为-2或2,那么就说明cur此时不为新增结点,而是向上更新时,cur变为了cur的某个祖先。

当cur不为新增结点时:
如下图所示:
在这里插入图片描述
我们先插入3结点到6的左边,此时cur是指向的3,而parent指向的是6,此时我们要更新平衡因子,3的为 0,6的为-1,然后我们发现cur的父亲6的平衡因子是-1或者1时,我们要继续往上更新.

cur = parent;
parent = parent->m_parent;

此时cur就指向的6,而parent指向的15,这个时候cur就变为了新增结点的祖先了,此时我们更新parent的平衡因子,由于新插入的结点在parent的左边,此时我们就需要–parent的平衡因子,就变为了-2,此时我们就需要旋转,如上图所示。
但上图的右下角的平衡因子的计算与我博客中写的计算方式是相反的,即左子树高度-右子树高度,其他操作完全相同。

代码如下:

pair<node*,bool> insert(const pair<K, V>& kv)
	{
		if (m_root == nullptr)		//1、如果平衡二叉树为空时,直接再我们的根结点指针创建一个结点
		{
			this->m_root = new node(kv);
			return make_pair(m_root,true);
		}
		else
		{
			//2、如果不为空,我们就按搜索树的规则进行寻找一个位置进行插入
			node* parent = nullptr;					
			node* cur = m_root;
			while (cur != nullptr)
			{
				if (cur->m_kv.first < kv.first)
				{
					parent = cur;
					cur = cur->m_right;
				}
				else if (cur->m_kv.first > kv.first)
				{
					parent = cur;
					cur = cur->m_left;
				}
				else
				{
					return make_pair(cur,false);
				}
			}
			//这里的parent不会造成空指针崩溃的情况
			//当头指针就与我们插入的元素相同时,在上面则会直接return false,不会来到这一步
			//到了这里判断我们要插入的值是比我们的parent值谁的大
			//如果比我的key值大,则创建一个新的结点插入到parent右边。否则插入到parent左边。
			node*newnode = new node(kv);
			cur = newnode;
			if (parent->m_kv.first < kv.first)
			{
				parent->m_right = cur;
				cur->m_parent = parent;
			}
			else
			{
				parent->m_left = cur;
				cur->m_parent = parent;
			}


			//3、更新我们的平衡因子
			while (parent != nullptr)	//我们要一直更新,有可能更新到根结点,如果更新到根结点时,根结点的parent为nullptr,此时就停止循环
			{
				if (parent->m_left == cur)	//如果在我们的左边添加的结点我们就--,右边++
				{
					--parent->m_bf;
				}
				else
				{
					++parent->m_bf;
				}

				if (parent->m_bf == 0)	//如果此时的平衡因子为0了,说明此时已经是平衡的了,不需要任何的处理
				{
					break;
				}
				else if (parent->m_bf== 1 || parent->m_bf== -1)	//如果此时为 -1 或者 1时
																//就说明此时某一边填加了一个新的结点,有可能导致上层结点不平衡
																//我们要继续往上更新平衡因子
				{
					cur = parent;
					parent = parent->m_parent;
				}
				else if (parent->m_bf == 2 || parent->m_bf == -2)	//如果等于 2 或 -2时,那么此时就会发现此时我们的平衡搜索二叉树不平衡了
																	//我们要进行旋转, 让它重新变的平衡
				{
					if (parent->m_bf == 2)		
					{
						if (cur->m_bf == 1)  //左旋 路径是直线
						{
							RotateL(parent);
						}
						else if (cur->m_bf == -1) //右左双旋 路径是折线
						{
							RotateRL(parent);
						}

					}
					else if(parent->m_bf== -2)
					{
						if (cur->m_bf == -1)   //右旋 路径是直线
						{
							RotateR(parent);
						}
						else if(cur->m_bf == 1) 左右双旋 路径是折线
						{
							RotateLR(parent);
						}
					}

					//旋转完成后,parent所在的树的高度恢复到了插入节点前高度
					//如果是子树,对上层没有影响,更新结束。
					break;
				}

			}
			return make_pair(newnode, true);
		}
	}

AVL树的旋转

根据上面的分析我们可以得到以下几种旋转的情况:

  • 当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋。
  • 当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋。
  • 当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋。
  • 当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋。

其中前两种是直线,所以进行单旋即可,二后面两种情况是折线,需要双旋才能平衡。

左单旋

具象图动图演示:
在这里插入图片描述
具象图旋转示意图如下:
在这里插入图片描述
左旋的步骤:
如上图所示:

  1. 先让subRL做parent的右子树。
  2. 再将parent做subR的左子树。
  3. 判断parent是否是根的位置,如果为根,那么就让subR做新的根,否则让parent之前的父亲链接上subR。
  4. 最后更新平衡因子。

代码如下:

//传进来的是不平衡的结点的指针
void RotateL(node* parent)	//左旋
{
	node* subR = parent->m_right;	//出问题的右结点
	node* subRL = subR->m_left;		//出问题的右结点的左结点

	parent->m_right = subRL;		//首先先将我们的parent的右指向subRL
	if (subRL != nullptr)			//再将subRL的父亲指向parent结点,但这样要判断一下是否是空指针,如果subRL是空指针的话,
									//那么解引用它会出现问题
	{
		subRL->m_parent = parent;
	}

	node* curParent = parent->m_parent;	//拿一个结点存储parent的父亲
	parent->m_parent = subR;			//再使得parent的父亲指针指向它原先的右结点(subR)

	subR->m_left = parent;				//在让subR的左指向parent
	if (parent == m_root)				//在这里得判断一下它是否为根,如果parent为根的话,那么我们的根结点指针也得改变
										//并且将subR的父亲指针置为空,此时subR此时为根结点
	{
		m_root = subR;
		subR->m_parent = nullptr;
	}
	else
	{									//如果不为头节点,那么我们只需要将subR的父亲指针指向parent之前的父亲结点
		if (curParent->m_left == parent)
		{
			curParent->m_left = subR;
		}
		else
		{
			curParent->m_right = subR;
		}
		subR->m_parent = curParent;
	}
	subR->m_bf = parent->m_bf = 0;	//更新平衡因子
}
右单旋

具象图动图演示:
在这里插入图片描述

具象图旋转示意图如下:
在这里插入图片描述
右旋的步骤:
如上图所示:

  1. 先让subLR做parent的左子树。
  2. 再将parent做subL的右子树。
  3. 判断parent是否是根的位置,如果为根,那么就让subL做新的根,否则让parent之前的父亲链接上subL。
  4. 最后更新平衡因子。

代码如下:

void RotateR(node* parent)	//右旋
{
	node* subL = parent->m_left;
	node* subLR = subL->m_right;

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

	node* curParent = parent->m_parent;
	parent->m_parent = subL;

	subL->m_right = parent;
	if (parent==m_root)
	{
		m_root = subL;
		subL->m_parent = nullptr;
	}
	else
	{
		if (curParent->m_left == parent)
		{
			curParent->m_left = subL;
		}
		else
		{
			curParent->m_right = subL;
		}
		subL->m_parent = curParent;
	}
	//为什么subLR的平衡因子不需要改变呢?
	//因为它的左右子树的平衡并没有被改变,所以不需要改变其平衡因子。
	//只需要更改这里出现问题的parent结点与subL结点的平衡因子
	subL->m_bf = parent->m_bf = 0;	//更新平衡因子
}

左右双旋

具象图动图演示:
在这里插入图片描述

具象图旋转示意图如下:
1、首先插入一个新结点
在这里插入图片描述
2、再以30为旋转点先进行左旋
在这里插入图片描述
3、最后在以40为旋转点进行右旋
在这里插入图片描述
4、最后更新平衡因子
这里的subLR点为40这个点
这里平衡因子左右双旋后,平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况:

  1. 当subLR原始平衡因子是-1时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0。
  2. 当subLR原始平衡因子是1时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0。
  3. 当subLR原始平衡因子是0时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0。

代码如下:

void RotateLR(node* parent)
{
	node* subL = parent->m_left;
	node* subLR = subL->m_right;
	int bf = subLR->m_bf;

	RotateL(subL);
	RotateR(parent);

	if (bf == 1)
	{
		parent->m_bf = 0;
		subL->m_bf = -1;
		subLR->m_bf = 0;
	}
	else if (bf == -1)
	{
		parent->m_bf = 1;
		subL->m_bf = 0;
		subLR->m_bf = 0;
	}
	else if (bf == 0)
	{
		parent->m_bf = 0;
		subL->m_bf = 0;
		subLR->m_bf = 0;
	}
}
右左双旋

具象图动图演示:

在这里插入图片描述
具象图旋转示意图如下:
1、先插入一个新结点
在这里插入图片描述
2、在以50为旋转点来进行旋转
在这里插入图片描述
此时平衡因子
3、最后以40为旋转点进行旋转
在这里插入图片描述
4、同理这里也是更新平衡因子,同样有三种情况:
此时这里的subRL是40这个结点。

  1. 当subRL原始平衡因子是1时,左右双旋后parent、subR、subRL的平衡因子分别更新为-1、0、0。
  2. 当subRL原始平衡因子是-1时,左右双旋后parent、subR、subRL的平衡因子分别更新为0、1、0。
  3. 当subRL原始平衡因子是-1时,左右双旋后parent、subR、subRL的平衡因子分别更新为0、1、0。
    代码如下:
void RotateRL(node* parent)
{
	node* subR = parent->m_right;
	node* subRL = subR->m_left;
	int bf = subRL->m_bf;

	RotateR(subR);
	RotateL(parent);
	if (bf == 1)
	{
		parent->m_bf = -1;
		subRL->m_bf = 0;
		subR->m_bf = 0;
	}
	else if (bf == -1)
	{
		parent->m_bf = 0;
		subRL->m_bf = 0;
		subR->m_bf = 1;
	}
	else if (bf == 0)
	{
		parent->m_bf = 0;
		subRL->m_bf = 0;
		subR->m_bf = 0;
	}
}

AVL树删除

AVL树得删除,必须先利用key值找到我们要删除得结点。
此时删除结点有以下几种情况:
1、删除的是叶子结点,此时直接删除即可,再更新平衡因子。
2、删除的不是叶子结点,是度为1的结点,即左子树或者右子树一边为空的结点时,只需要将这个结点父亲的的左右指针的一个去指向该结点不为空的一边即可,最后更新平衡因子。
3、删除的结点左右都不为空时,此时我们就需要找一个结点来替换该结点来删除,这里我们采用当前删除结点的右子树的最左节点来替换删除。那么最左结点也只可能为叶子结点或者度为1的结点,故转变为了前两种情况。

综上所述:即最终删除的所有情况都转化为了删除叶子结点或者是度为1的结点。

知道了怎么删除结点,那么平衡因子要怎么调节呢?

  • 删除的结点在parent的右边,parent的平衡因子−−。
  • 删除的结点在parent的左边,parent的平衡因子++。

此时parent的平衡因子有以下3种情况:

parent更新后的平衡因子分析
0只有当父亲平衡因子变化之前是-1/1时,经过–/++操作后会变成0,说明原来parent的左右子树的高度差为1,现在删除的是parent结点左右子树高的一边,此时会影响到parent的父结点及其祖先结点的平衡因子,因此需要继续往上更新平衡因子。
-1/1只有当父亲变化前是0,经过–/++操作后会变成-1/1,说明原来parent的左子树和右子树高度相同,现在我们删除一个结点,并不会影响以parent为根结点的子树的高度,从而变化影响parent的父结点的平衡因子,因此无需继续往上更新平衡因子。
-2/2只有当父亲平衡因子变化之前是-1/1时,经过–/++操作变成-2/2,说明原来parent的左右子树的高度差为1,现在删除的是parent结点左右子树矮的一边,此时会影响到parent的父结点及其祖先结点的平衡因子,因此需要继续往上更新平衡因子。此时parent结点的左右子树高度之差的绝对值已经超过1了,不满足AVL树的要求,因此需要进行旋转处理。

此时在旋转处理情况有以下6种:

  1. 当parent的平衡因子为 -2,parent的左孩子的平衡因子为-1时,进行右单旋。
  2. 当parent的平衡因子为 -2,parent的左孩子的平衡因子为0时,也进行右单旋。
    具体情况如下图所示:
    删除结点为26,更新前此时parent结点为18,平衡因子为-2,而parent的左孩子结点的平衡因子为0。删除之后需要以parent为旋转点来进行一个右单旋,此时parent的平衡因子更新为-1,而parent的左孩子结点的平衡因子为1。此时旋转后无需继续往上更新平衡因子,因为这种情况旋转后树的高度并没有发生变化。
    在这里插入图片描述
  3. 当parent的平衡因子为 -2,parent的左孩子的平衡因子为1时,进行左右双旋。
  4. 当parent的平衡因子为 2,parent的右孩子的平衡因子为1时,进行左单旋。
  5. 当parent的平衡因子为 2,parent的右孩子的平衡因子为0时,也进行左单旋。

具体情况如下图所示:
我们要删除30这个结点,此时parent为50这个结点,parent的右孩子我们命名为parentRight,此时parentRight为60号结点,删除30这个结点之后,parent平衡因子为2了,此时需要看parentRight,此时parentRight为0,我们需要进行一个左单旋。parentRight的平衡因子更新完后变为-1,parent的平衡因子变为1。此时旋转后无需继续往上更新平衡因子,因为这种情况旋转后树的高度并没有发生变化。
在这里插入图片描述
6. 当parent的平衡因子为 2,parent的右孩子的平衡因子为-1时,进行右左双旋。

其他的4种情况的旋转与之前插入的情况相同。

代码如下:

bool Erase(const K& key)
{
	if (m_root == nullptr)
	{
		return false;
	}
	else
	{
		node* parent = nullptr;
		node* cur = m_root;
		node* delParent = nullptr;
		node* delNode = nullptr;
		while (cur != nullptr) //找删除的结点的位置
		{
			if (cur->m_kv.first < key)
			{
				parent = cur;
				cur = cur->m_right;
			}
			else if(cur->m_kv.first > key)
			{
				parent = cur;
				cur = cur->m_left;
			}
			else
			{
				//找到了结点的情况
				if (cur->m_left == nullptr)
				{
					if (cur == m_root)
					{
						m_root = m_root->m_right;
						if (m_root != nullptr) //这里是判断当左右都为空的时候,nullptr奔溃
						{
							m_root->m_parent = nullptr;
						}
						delete cur;
						return true;
					}
					else
					{
						delParent = parent;
						delNode = cur;
					}
				}
				else if (cur->m_right == nullptr)
				{
					if (cur == m_root)
					{
						m_root = cur->m_left;
						if (m_root != nullptr)
						{
							m_root->m_parent = nullptr;
						}
						delete cur;
						return true;
					}
					else
					{
						delParent = parent;
						delNode = cur;
					}
				}
				else
				{
					node* leftMinParent = cur;
					node* leftMin = cur->m_right;
					while (leftMin->m_left != nullptr) //找右半边的最左结点,为右边最小值
					{
						leftMinParent = leftMin;
						leftMin = leftMin->m_left;
					}
					cur->m_kv.first = leftMin->m_kv.first;
					cur->m_kv.second = leftMin->m_kv.second;
					delParent = leftMinParent;
					delNode = leftMin;
				}
				break;
			}
		}

		if (cur == nullptr) //没找到的情况
		{
			return false;
		}

		//更新平衡因子
		cur = delNode;
		parent = delParent;
		while (parent != nullptr) //最坏一路更新到根结点
		{
			if (cur == parent->m_left)  //判断删除的是在父亲的那一边,然后更新平衡因子
			{
				++parent->m_bf;
			}
			else if(cur == parent->m_right)
			{
				--parent->m_bf;
			}

			if (parent->m_bf == 1 || parent->m_bf == -1) //最坏一路更新到根结点
			{
				break;
			}
			else if (parent->m_bf == 0) //需要继续往上更新平衡因子
			{
				cur = parent;  //树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子
				parent = parent->m_parent;
			}
			else if (parent->m_bf == 2 || parent->m_bf == -2) //需要进行旋转
			{
				if (parent->m_bf == 2) //直线的情况
				{
					if (parent->m_right->m_bf == 1)
					{
						node* temp = parent->m_right; //保存当前根结点所在的位置
						RotateL(parent);
						parent = temp;
					}
					else if (parent->m_right->m_bf == -1) //折线
					{
						node* temp = parent->m_right->m_left;
						RotateRL(parent);
						parent = temp;
					}
					else if (parent->m_right->m_bf == 0) //RotateL(parent);
					{
						node* parentRight = parent->m_right;
						RotateL(parent);
						parent->m_bf = 1;
						parentRight->m_bf = -1;
						break;
					}
				}
				else if(parent->m_bf == -2)
				{
					if (parent->m_left->m_bf == -1)
					{
						node* temp = parent->m_left;
						RotateR(parent);
						parent = temp;
					}
					else if (parent->m_left->m_bf == 1)
					{
						node* temp = parent->m_left->m_right;
						RotateLR(parent);
						parent = temp;
					}
					else if (parent->m_left->m_bf == 0)  //RotateR(parent);
					{
						//这里不会删除不会影响到祖先的平衡因子,故直接退出
						node* parentLeft = parent->m_left;
						RotateR(parent);
						parent->m_bf = -1;
						parentLeft->m_bf = 1;
						break;
					}
				}
				//树的高度变化,会影响其上一层父结点父结点的平衡因子,需要继续往上更新平衡因子
				cur = parent;
				parent = parent->m_parent;
			}
		}


		if (delNode->m_left == nullptr) //实际删除结点的左子树为空  替换法删除与部分左为空情况的删除
		{
			if (delNode == delParent->m_left)  //实际删除结点是其父结点的左孩子
			{
				delParent->m_left = delNode->m_right;
				if (delNode->m_right != nullptr)
				{
					delNode->m_right->m_parent = delParent;
				}
			}
			else if(delNode == delParent->m_right)
			{
				delParent->m_right = delNode->m_right;
				if (delNode->m_right != nullptr)
				{
					delNode->m_right->m_parent = delParent;
				}
			}
		}
		else if (delNode->m_right == nullptr)
		{
			if (delNode == delParent->m_left)  //实际删除结点是其父结点的左孩子
			{
				delParent->m_left = delNode->m_left;
				if (delNode->m_left != nullptr)
				{
					delNode->m_left->m_parent = delParent;
				}
			}
			else if (delNode == delParent->m_right)
			{
				delParent->m_right = delNode->m_left;
				if (delNode->m_left != nullptr)
				{
					delNode->m_left->m_parent = delParent;
				}
			}
		}
		delete delNode;
		return true;
	}
}

我们可以看到,经过任何一种旋转后,树的高度没有发生变化,所以经过任何一种情况的旋转后无需继续往上更新平衡因子。

AVL树查找

AVL树的查找与普通的二叉搜索树的查找规则相同。
在这里插入图片描述
代码如下:

//查找函数
node* Find(const K& key)
{
	node* cur = m_root;
	while (cur)
	{
		if (key < cur->m_kv.first) //key值小于该结点的值
		{
			cur = cur->m_left; //在该结点的左子树当中查找
		}
		else if (key > cur->m_kv.first) //key值大于该结点的值
		{
			cur = cur->m_right; //在该结点的右子树当中查找
		}
		else //找到了目标结点
		{
			return cur; //返回该结点
		}
	}
	return nullptr; //查找失败
}

AVL树修改

方法一:
直接实现一个Modify函数,利用该函数来修改对应key值结点得value值。

bool Modify(const K& key, const V& val)
{
	node* ret = Find(key);
	if (ret == nullptr) //未找到指定key值的结点
	{
		return false;
	}
	ret->m_kv.second = val; //修改结点的value
	return true;
}

方法二:
与stl中的map容器重载的operator[] 类似,这里我们也重载operator[]模拟stl中的方式。
将插入函数的返回值设置为pair类型的,返回值的参数为如下情况:
1、若待插入结点的键值key在AVL树当中不存在,则结点插入成功,并返回插入后结点的指针和true。
2、若待插入结点的键值key在AVL树当中已经存在,则结点插入失败,并返回AVL树当中键值为key的结点的指针和false。
此时我们通过operator[] 函数调用insert函数返回回来的一个pair,此时当树中没有以这个值key值的结点或者之前就已经存在时,此时operator[] 就会返回insert函数返回回来的pair的第一个参数结点的指针中的m_kv的value的引用。

V& operator[](const K& key)
{
	pair<node*, bool>ret = insert(make_pair(key, V()));
	return ret.first->m_kv.second;
}

AVL树的验证

由于AVL树本质也是一棵二叉搜索树,此时它首先要满足的条件之一即是:
中序遍历二叉搜索树出来的序列是有序的。

private:
	void _Inorder( node* &root) 
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->m_left);
		cout << root->m_kv.first << " : " << root->m_kv.second << endl;
		_Inorder(root->m_right);
	}
public:
	void Inorder() 
	{
		_Inorder(m_root);
	}

在这里我们利用两个函数来进行书写,这样做的好处是,外层用户不会直接去使用到根结点,它只需要看到结果即可。
但中序遍历出来有序,只能保证它是一棵二叉搜索树,并不能保证它是AVL树的性质。此时我们要从后序遍历开始,验证每一棵子树是不是AVL树。
即要满足第二个性质:每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)。这里虽然有平衡因子能看左右子树是否平衡,但是显示效果不明显,但是为了显示的更加明显点,我们计算每棵子树的最大深度。

private:
	bool _isBalance(node* &root)	
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight = depth(root->m_left);
		int rightHeight = depth(root->m_right);

		return abs(leftHeight - rightHeight) < 2
			&& _isBalance(root->m_left)
			&& _isBalance(root->m_right);
	}
public:
	bool isBalance()	
	{
		return _isBalance(m_root);
	}

同理这里同样利用两个函数,一个对外公开,一个专门对内使用。

AVL树性能分析

AVL树是一棵绝对平衡的二叉搜索树,其要求必须每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度。但是如果要经常对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多。
AVL树的增删查改的时间复杂度都为O(logN),时间效率是很高的,但由于要经常对树的结构去修改,虽然红黑树与AVL树的时间复杂度O(logN),但性能不一定有红黑树高效。

完整代码如下:

#pragma once
#include<iostream>
#include<stack>
#include<algorithm>
using namespace std;

template<class K,class V>
struct AVLTreeNode	//三叉链
{
	AVLTreeNode* m_left;
	AVLTreeNode* m_right;
	AVLTreeNode* m_parent;

	pair<K, V>m_kv;
	int m_bf;	//平衡因子


	AVLTreeNode(const pair<K, V>&kv)
		: m_left(nullptr), m_right(nullptr), m_parent(nullptr)
		, m_kv(kv), m_bf(0)
	{}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V>node;
public:
	AVLTree():m_root(nullptr)
	{}

	AVLTree(const AVLTree<K,V>& obj)
	{
		m_root = copy(obj.m_root,nullptr);
	}

	AVLTree<K,V>& operator=(const AVLTree<K,V>& obj)
	{
		if (this != &obj)
		{
			AVLTree<K,V>temp = obj;	//调用拷贝构造
			std::swap(temp.m_root, this->m_root);
		}
		return *this;
	}

	node* copy( node* root,node*parent) //递归利用中序来做深拷贝
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		node* copyNode = new node(root->m_kv);
		copyNode->m_parent = parent;
		copyNode->m_left = copy(root->m_left,copyNode );
		copyNode->m_right = copy(root->m_right,copyNode );
		return copyNode;
	}

	~AVLTree() //利用后序来释放结点
	{
		if (m_root == nullptr)
		{
			return;
		}
		node* cur = m_root;
		node* prev = nullptr;
		stack<node*>s;
		while (cur != nullptr || !s.empty())
		{
			while (cur != nullptr)	//一直把左结点放进栈中
			{
				s.push(cur);
				cur = cur->m_left;
			}
			cur = s.top();
			s.pop();
			//cur->m_right==prev 这个判断很重要,防止访问完右结点时,来回横跳
			if (cur->m_right == nullptr || cur->m_right == prev)
			{
				prev = cur;
				cur->m_bf = 0;
				cur->m_left = nullptr;
				cur->m_parent = nullptr;
				cur->m_right = nullptr;
				delete cur;
				cur = nullptr;
			}
			else
			{
				s.push(cur);
				cur = cur->m_right;
			}
		}
	}

	//传进来的是不平衡的结点的指针
	void RotateL(node* parent)	//左旋
	{
		node* subR = parent->m_right;	//出问题的右结点
		node* subRL = subR->m_left;		//出问题的右结点的左结点

		parent->m_right = subRL;		//首先先将我们的parent的右指向subRL
		if (subRL != nullptr)			//再将subRL的父亲指向parent结点,但这样要判断一下是否是空指针,如果subRL是空指针的话,
										//那么解引用它会出现问题
		{
			subRL->m_parent = parent;
		}

		node* curParent = parent->m_parent;	//拿一个结点存储parent的父亲
		parent->m_parent = subR;			//再使得parent的父亲指针指向它原先的右结点(subR)

		subR->m_left = parent;				//在让subR的左指向parent
		if (parent == m_root)				//在这里得判断一下它是否为根,如果parent为根的话,那么我们的根结点指针也得改变
											//并且将subR的父亲指针置为空,此时subR此时为根结点
		{
			m_root = subR;
			subR->m_parent = nullptr;
		}
		else
		{									//如果不为头节点,那么我们只需要将subR的父亲指针指向parent之前的父亲结点
			if (curParent->m_left == parent)
			{
				curParent->m_left = subR;
			}
			else
			{
				curParent->m_right = subR;
			}
			subR->m_parent = curParent;
		}
		subR->m_bf = parent->m_bf = 0;	//更新平衡因子
	}

	void RotateLR(node* parent)
	{
		node* subL = parent->m_left;
		node* subLR = subL->m_right;
		int bf = subLR->m_bf;

		RotateL(subL);
		RotateR(parent);

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

	void RotateR(node* parent)	//右旋
	{
		node* subL = parent->m_left;
		node* subLR = subL->m_right;

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

		node* curParent = parent->m_parent;
		parent->m_parent = subL;

		subL->m_right = parent;
		if (parent==m_root)
		{
			m_root = subL;
			subL->m_parent = nullptr;
		}
		else
		{
			if (curParent->m_left == parent)
			{
				curParent->m_left = subL;
			}
			else
			{
				curParent->m_right = subL;
			}
			subL->m_parent = curParent;
		}
		//为什么subLR的平衡因子不需要改变呢?
		//因为它的左右子树的平衡并没有被改变,所以不需要改变其平衡因子。
		//只需要更改这里出现问题的parent结点与subL结点的平衡因子
		subL->m_bf = parent->m_bf = 0;	//更新平衡因子
	}

		void RotateRL(node* parent)
		{
			node* subR = parent->m_right;
			node* subRL = subR->m_left;
			int bf = subRL->m_bf;

			RotateR(subR);
			RotateL(parent);
			if (bf == 1)
			{
				parent->m_bf = -1;
				subRL->m_bf = 0;
				subR->m_bf = 0;
			}
			else if (bf == -1)
			{
				parent->m_bf = 0;
				subRL->m_bf = 0;
				subR->m_bf = 1;
			}
			else if (bf == 0)
			{
				parent->m_bf = 0;
				subRL->m_bf = 0;
				subR->m_bf = 0;
			}
		}
	V& operator[](const K& key)
	{
		pair<node*, bool>ret = insert(make_pair(key, V()));
		return ret.first->m_kv.second;
	}


	pair<node*,bool> insert(const pair<K, V>& kv)
	{
		if (m_root == nullptr)		//1、如果平衡二叉树为空时,直接再我们的根结点指针创建一个结点
		{
			this->m_root = new node(kv);
			return make_pair(m_root,true);
		}
		else
		{
			//2、如果不为空,我们就按搜索树的规则进行寻找一个位置进行插入
			node* parent = nullptr;					
			node* cur = m_root;
			while (cur != nullptr)
			{
				if (cur->m_kv.first < kv.first)
				{
					parent = cur;
					cur = cur->m_right;
				}
				else if (cur->m_kv.first > kv.first)
				{
					parent = cur;
					cur = cur->m_left;
				}
				else
				{
					return make_pair(cur,false);
				}
			}
			//这里的parent不会造成空指针崩溃的情况
			//当头指针就与我们插入的元素相同时,在上面则会直接return false,不会来到这一步
			//到了这里判断我们要插入的值是比我们的parent值谁的大
			//如果比我的key值大,则创建一个新的结点插入到parent右边。否则插入到parent左边。
			node*newnode = new node(kv);
			cur = newnode;
			if (parent->m_kv.first < kv.first)
			{
				parent->m_right = cur;
				cur->m_parent = parent;
			}
			else
			{
				parent->m_left = cur;
				cur->m_parent = parent;
			}


			//3、更新我们的平衡因子
			while (parent != nullptr)	//我们要一直更新,有可能更新到根结点,如果更新到根结点时,根结点的parent为nullptr,此时就停止循环
			{
				if (parent->m_left == cur)	//如果在我们的左边添加的结点我们就--,右边++
				{
					--parent->m_bf;
				}
				else
				{
					++parent->m_bf;
				}

				if (parent->m_bf == 0)	//如果此时的平衡因子为0了,说明此时已经是平衡的了,不需要任何的处理
				{
					break;
				}
				else if (parent->m_bf== 1 || parent->m_bf== -1)	//如果此时为 -1 或者 1时
																//就说明此时某一边填加了一个新的结点,有可能导致上层结点不平衡
																//我们要继续往上更新平衡因子
				{
					cur = parent;
					parent = parent->m_parent;
				}
				else if (parent->m_bf == 2 || parent->m_bf == -2)	//如果等于 2 或 -2时,那么此时就会发现此时我们的平衡搜索二叉树不平衡了
																	//我们要进行旋转, 让它重新变的平衡
				{
					if (parent->m_bf == 2)		
					{
						if (cur->m_bf == 1)  //左旋 路径是直线
						{
							RotateL(parent);
						}
						else if (cur->m_bf == -1) //右左双旋 路径是折线
						{
							RotateRL(parent);
						}

					}
					else if(parent->m_bf== -2)
					{
						if (cur->m_bf == -1)   //右旋 路径是直线
						{
							RotateR(parent);
						}
						else if(cur->m_bf == 1) 左右双旋 路径是折线
						{
							RotateLR(parent);
						}
					}

					//旋转完成后,parent所在的树的高度恢复到了插入节点前高度
					//如果是子树,对上层没有影响,更新结束。
					break;
				}

			}
			return make_pair(newnode, true);
		}
	}
	int depth(node*& root) 
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = depth(root->m_left);
		int rightHeight = depth(root->m_right);
		return max(depth(root->m_left), depth(root->m_right)) + 1;
		//leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
private:
	bool _isBalance(node* &root)	
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight = depth(root->m_left);
		int rightHeight = depth(root->m_right);

		return abs(leftHeight - rightHeight) < 2
			&& _isBalance(root->m_left)
			&& _isBalance(root->m_right);
	}
public:
	bool isBalance()	
	{
		return _isBalance(m_root);
	}
private:
	void _Inorder( node* &root) 
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->m_left);
		cout << root->m_kv.first << " : " << root->m_kv.second << endl;
		_Inorder(root->m_right);
	}
public:
	void Inorder() 
	{
		_Inorder(m_root);
	}

	bool Erase(const K& key)
	{
		if (m_root == nullptr)
		{
			return false;
		}
		else
		{
			node* parent = nullptr;
			node* cur = m_root;
			node* delParent = nullptr;
			node* delNode = nullptr;
			while (cur != nullptr) //找删除的结点的位置
			{
				if (cur->m_kv.first < key)
				{
					parent = cur;
					cur = cur->m_right;
				}
				else if(cur->m_kv.first > key)
				{
					parent = cur;
					cur = cur->m_left;
				}
				else
				{
					//找到了结点的情况
					if (cur->m_left == nullptr)
					{
						if (cur == m_root)
						{
							m_root = m_root->m_right;
							if (m_root != nullptr) //这里是判断当左右都为空的时候,nullptr奔溃
							{
								m_root->m_parent = nullptr;
							}
							delete cur;
							return true;
						}
						else
						{
							delParent = parent;
							delNode = cur;
						}
					}
					else if (cur->m_right == nullptr)
					{
						if (cur == m_root)
						{
							m_root = cur->m_left;
							if (m_root != nullptr)
							{
								m_root->m_parent = nullptr;
							}
							delete cur;
							return true;
						}
						else
						{
							delParent = parent;
							delNode = cur;
						}
					}
					else
					{
						node* leftMinParent = cur;
						node* leftMin = cur->m_right;
						while (leftMin->m_left != nullptr) //找右半边的最左结点,为右边最小值
						{
							leftMinParent = leftMin;
							leftMin = leftMin->m_left;
						}
						cur->m_kv.first = leftMin->m_kv.first;
						cur->m_kv.second = leftMin->m_kv.second;
						delParent = leftMinParent;
						delNode = leftMin;
					}
					break;
				}
			}

			if (cur == nullptr) //没找到的情况
			{
				return false;
			}

			//更新平衡因子
			cur = delNode;
			parent = delParent;
			while (parent != nullptr) //最坏一路更新到根结点
			{
				if (cur == parent->m_left)  //判断删除的是在父亲的那一边,然后更新平衡因子
				{
					++parent->m_bf;
				}
				else if(cur == parent->m_right)
				{
					--parent->m_bf;
				}

				if (parent->m_bf == 1 || parent->m_bf == -1) //最坏一路更新到根结点
				{
					break;
				}
				else if (parent->m_bf == 0) //需要继续往上更新平衡因子
				{
					cur = parent;  //树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子
					parent = parent->m_parent;
				}
				else if (parent->m_bf == 2 || parent->m_bf == -2) //需要进行旋转
				{
					if (parent->m_bf == 2) //直线的情况
					{
						if (parent->m_right->m_bf == 1)
						{
							node* temp = parent->m_right; //保存当前根结点所在的位置
							RotateL(parent);
							parent = temp;
						}
						else if (parent->m_right->m_bf == -1) //折线
						{
							node* temp = parent->m_right->m_left;
							RotateRL(parent);
							parent = temp;
						}
						else if (parent->m_right->m_bf == 0) //RotateL(parent);
						{
							node* parentRight = parent->m_right;
							RotateL(parent);
							parent->m_bf = 1;
							parentRight->m_bf = -1;
							break;
							//node* grandfather = parent->m_parent;
							//node* parentRight = parent->m_right;
							//if (grandfather == nullptr) //此时parent为根
							//{
							//	parent->m_right = parentRight->m_left;
							//	parentRight->m_left->m_parent = parent;

							//	parent->m_parent = parentRight;
							//	parentRight->m_left = parent;

							//	parentRight->m_parent = nullptr;
							//	m_root = parentRight;
							//	parent->m_left = nullptr;

							//	//RotateL(parent);
							//	parentRight->m_bf = -1;
							//	parent->m_bf = 1;
							//}
							//else
							//{
							//	if (parent == grandfather->m_right)
							//	{
							//		grandfather->m_right = parentRight;
							//		parentRight->m_parent = grandfather;

							//		parent->m_right = parentRight->m_left;
							//		parentRight->m_left->m_parent = parent;

							//		
							//		parent->m_parent = parentRight;
							//		parentRight->m_left = parent;
							//		parent->m_left = nullptr;

							//		parentRight->m_bf = -1;
							//		parent->m_bf = 1;
							//		//RotateL(parent);
							//	}
							//	else
							//	{
							//		grandfather->m_left = parentRight;
							//		parentRight->m_parent = grandfather;

							//		parent->m_right = parentRight->m_left;
							//		parentRight->m_left->m_parent = parent;

							//		parent->m_parent = parentRight;
							//		parentRight->m_left = parent;
							//		parent->m_left = nullptr;
							//		parentRight->m_bf = -1;
							//		parent->m_bf = 1;
							//		//RotateL(parent);
							//	}
							//}
							//break;
						}
					}
					else if(parent->m_bf == -2)
					{
						if (parent->m_left->m_bf == -1)
						{
							node* temp = parent->m_left;
							RotateR(parent);
							parent = temp;
						}
						else if (parent->m_left->m_bf == 1)
						{
							node* temp = parent->m_left->m_right;
							RotateLR(parent);
							parent = temp;
						}
						else if (parent->m_left->m_bf == 0)  //RotateR(parent);
						{
							node* parentLeft = parent->m_left;
							RotateR(parent);
							parent->m_bf = -1;
							parentLeft->m_bf = 1;
							break;

							//node* grandfather = parent->m_parent;
							//node* parentLeft = parent->m_left;
							//if (grandfather == nullptr)  //此时parent为根
							//{
							//	parent->m_left = parentLeft->m_right;
							//	parentLeft->m_right->m_parent = parent;

							//	parentLeft->m_right = parent;
							//	parent->m_parent = parentLeft;

							//	parentLeft->m_parent = nullptr;
							//	m_root = parentLeft;
							//	parent->m_right = nullptr;

							//	parent->m_bf = -1;
							//	parentLeft->m_bf = 1;
							//}
							//else
							//{
							//	//RotateR(parent);
							//	if (parent == grandfather->m_right) //父亲结点在祖父结点的右边
							//	{
							//		grandfather->m_right = parentLeft;
							//		parentLeft->m_parent = grandfather;

							//		parent->m_left = parentLeft->m_right;
							//		parentLeft->m_right->m_parent = parent;

							//		parentLeft->m_right = parent;
							//		parent->m_parent = parentLeft;
							//		parent->m_right = nullptr;

							//		parent->m_bf = -1;
							//		parentLeft->m_bf = 1;
							//	}
							//	else //RotateR(parent);
							//	{
							//		grandfather->m_left = parentLeft;
							//		parentLeft->m_parent = grandfather;

							//		parent->m_left = parentLeft->m_right;
							//		parentLeft->m_right->m_parent = parent;

							//		parentLeft->m_right = parent;
							//		parent->m_parent = parentLeft;
							//		parent->m_right = nullptr;
							//		parent->m_bf = -1;
							//		parentLeft->m_bf = 1;
							//	}
							//}
							
							break;
						}
					}
					//树的高度变化,会影响其上一层父结点父结点的平衡因子,需要继续往上更新平衡因子
					cur = parent;
					parent = parent->m_parent;
				}
			}


			if (delNode->m_left == nullptr) //实际删除结点的左子树为空  替换法删除与部分左为空情况的删除
			{
				if (delNode == delParent->m_left)  //实际删除结点是其父结点的左孩子
				{
					delParent->m_left = delNode->m_right;
					if (delNode->m_right != nullptr)
					{
						delNode->m_right->m_parent = delParent;
					}
				}
				else if(delNode == delParent->m_right)
				{
					delParent->m_right = delNode->m_right;
					if (delNode->m_right != nullptr)
					{
						delNode->m_right->m_parent = delParent;
					}
				}
			}
			else if (delNode->m_right == nullptr)
			{
				if (delNode == delParent->m_left)  //实际删除结点是其父结点的左孩子
				{
					delParent->m_left = delNode->m_left;
					if (delNode->m_left != nullptr)
					{
						delNode->m_left->m_parent = delParent;
					}
				}
				else if (delNode == delParent->m_right)
				{
					delParent->m_right = delNode->m_left;
					if (delNode->m_left != nullptr)
					{
						delNode->m_left->m_parent = delParent;
					}
				}
			}
			delete delNode;
			return true;
		}
	}
	//查找函数
	node* Find(const K& key)
	{
		node* cur = m_root;
		while (cur)
		{
			if (key < cur->m_kv.first) //key值小于该结点的值
			{
				cur = cur->m_left; //在该结点的左子树当中查找
			}
			else if (key > cur->m_kv.first) //key值大于该结点的值
			{
				cur = cur->m_right; //在该结点的右子树当中查找
			}
			else //找到了目标结点
			{
				return cur; //返回该结点
			}
		}
		return nullptr; //查找失败
	}
	//修改函数
	bool Modify(const K& key, const V& val)
	{
		node* ret = Find(key);
		if (ret == nullptr) //未找到指定key值的结点
		{
			return false;
		}
		ret->m_kv.second = val; //修改结点的value
		return true;
	}

private:
	node* m_root;	//根结点
};

在上面的动图中,如果大家也想向我一样弄那些动图,并来看看增删查改的过程且想弄AVL树的可视化可以点击下面这个网站。
AVL树可视化网站

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值