数据结构 : AVL树的原理以及实现(C++)


AVL树

AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G.
M. Adelson-Velsky和E. M. Landis

AVL树其实就是在二叉搜索树的基础上,引入了平衡因子的概念,通过旋转来调整平衡因子,使得二叉树始终平衡,效率更高。

特点

  1. 本身首先是一棵二叉搜索树。
  2. 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

二叉搜索树的实现的博客之前写过,所以这里就直接对以前的代码进行改造。
数据结构 : 二叉搜索树的原理以及实现(C++)


实现思路

数据结构

这里不仅需要左右子树,因为涉及到了大量的平衡因子调节,所以还需要保存父节点的指针,要用到三叉链的结构。

	struct AVLTreeNode
	{
		AVLTreeNode(const std::pair<K, V>& kv)
			: _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _kv(kv)
			, _bf(0)
		{}

		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		std::pair<K, V> _kv;	//键值对
		int _bf;	//平衡因子
	};

查找

AVL树本质还是二叉搜索树,所以查找部分可以直接复用,不需要修改。

直接从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树,相同则返回。如果遍历完还没找到,则说明不存在此树中,返回nullptr

Node* Find(const K& key)
{
	//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树
	Node* cur = _root;

	while (cur)
	{
		//比根节点大则查找右子树
		if (key > cur->_kv.first)
		{
			cur = cur->_right;
		}
		//比根节点小则查找左子树
		else if (key < cur->_kv.first)
		{
			cur = cur->_left;
		}
		//相同则返回
		else
		{
			return cur;
		}
	}

	//遍历完则说明查找不到,返回false
	return nullptr;
}

要讲插入和删除之前,就必须得讲他们两个的核心步骤,旋转和平衡因子的调节。AVL树正是通过这两个步骤来实现其高度平衡的特性。

平衡因子

平衡因子,其实就是左右子树的高度差。AVL树通过控制高度差不超过2,来实现平衡。

通常认为在右边插入节点时,平衡因子+1,左边插入时平衡因子减一。
例如:
在这里插入图片描述
右子树插入一个90,根节点平衡因子+1
在这里插入图片描述

当某节点平衡因子为0时,说明他的左右子树平衡
当平衡因子为1或者-1时,说明左右子树存在高度差,其父节点可能存在不平衡,需要向上继续判断。
当平衡因子为2或者-2时,说明此时不平衡,需要旋转处理。

if (parent->_bf == 0)
{
	break;
}
//高度发生变化,要继续往上判断
else if (parent->_bf == 1 || parent->_bf == -1)
{
	cur = parent;
	parent = parent->_parent;
}
//此时不平衡,需要旋转
else if (parent->_bf == 2 || parent->_bf == -2)
{
	//旋转分四种情况,直线单旋,折线双旋

	if (parent->_bf == 2)
	{
		//如果右边不平衡,并且子节点也是右边偏重,则左单旋
		if (cur->_bf == 1)
		{
			RotateL(parent);
		}
		//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
		else
		{
			RotateRL(parent);
		}
	}
	else
	{
		//左边不平衡,并且子节点也是左边偏重,右单旋
		if (cur->_bf == -1)
		{
			RotateR(parent);
		}
		//同上,左右双旋
		else
		{
			RotateLR(parent);
		}
	}

	//旋转完后恢复平衡,更新结束。
	break;
}

旋转

旋转分为四种情景,简单点总结的话就是如果节点呈直线则单旋,折线则双旋,下面一一分析。

首先讨论直线状态的单旋。
在这里插入图片描述
直线状态,也就是某一边不平衡,并且其那一边的子节点也是朝那一边偏重。
例如这个图,左边不平衡,子节点30也是左边偏重,则单旋。

右旋

例如这种情况,当左边不平衡,并且节点呈直线时(左节点的左边偏重),说明需要右旋处理。
在这里插入图片描述
此时节点60的平衡因子为-2,说明此时60的位置不平衡,需要旋转,由于是左边偏重,则需要将60向右旋转来恢复平衡。
在这里插入图片描述
这就是最简单的右旋。但是通常情况下,这些节点还有各自的子树。
在这里插入图片描述
还是按照上面的旋转方法,但是要在保持原有结构的情况下稍微处理。

对于节点60,如果要将其右旋,就需要让他变成30的右节点,但是30的右节点本来就有数据,所以此时就需要将30的右节点40放到别的位置。那么应该放到哪个位置呢?很简单,因为我们需要将60右旋,所以原本是他左节点的30变成了他的父节点,而他左节点的位置空了出来,所以就可以将40放到60的左边。这时再将60整个变为30的右节点即可。并且因为此时原本的父节点60变为了30的子节点,所以还要处理其与祖父节点的关系。
在这里插入图片描述
旋转结束后,调整30和60的平衡因子为0

总结一下:
右旋主要分为3个步骤

  1. 让不平衡的结点parent的左子树变为其原本左子树subL的右节点subLR
  2. 让parent变为subL的右子树
  3. 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
//右旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;

	//如果subLR存在,则让他的父节点指向parent。
	if (subLR)
	{
		subLR->_parent = parent;
	}

	subL->_right = parent;

	Node* ppNode = parent->_parent;
	parent->_parent = subL;

	//两种情况
	//如果parent为根节点,则让subL成为新的根节点
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	//如果不是根节点,则改变subL与其祖父节点的指向关系
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}

		subL->_parent = ppNode;
	}

	//左旋完成后平衡,平衡因子归零。
	subL->_bf = parent->_bf = 0;
}

左旋

左旋的思路和右旋一样,只是将顺序翻了过来。
例如这种情况,当右边不平衡,并且节点呈直线时(右节点的右边偏重),说明需要左旋处理。
在这里插入图片描述
在这里插入图片描述
这就是最简单的左旋。

下面看看复杂的左旋
在这里插入图片描述
还是同样的思路。只是这次把左右调换
左旋的步骤:

  1. 让不平衡的结点parent的右子树变为其原本右子树subR的右节点subRL
  2. 让parent变为subL的左子树
  3. 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
    在这里插入图片描述
//左旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;

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

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

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

		subR->_parent = ppNode;
	}

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

下面讨论双旋的情景,双旋发生在折线状态时。
例如
在这里插入图片描述
当某边不平衡,并且对于方向的子树,其反方向偏重时,需要双旋。
例如上图,左边不平衡,而左子树则是右边偏重。此时呈折线状态。

右左双旋

在这里插入图片描述
继续刚刚那个图,可以看到,他呈折线状态,与前面单旋的状态都不同。所以此时我们可以换个思路,可以先对其进行一次单旋,将其转换成之前的直线状态,再进行一次单旋即可平衡。

因为其右子树是左边偏重,所以对其右子树先进行一次右旋。
在这里插入图片描述
可以看到,此时就恢复成了原本直线的状态,此时因为30节点右边不平衡,所以再进行一次左旋即可完成。
在这里插入图片描述
但是这里并不能直接进行一次右单旋然后左单旋,这里有两种情况,如果40插入在了60的左子树,则是上面那种情况,调整完后subR平衡因子为1,parent和subRL为0。
但是如果插入在了右子树,则又是另一种情况。
下面画图
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如果40插入在了60的右子树,则调整完后parent平衡因子为-1,subR和subRL为0。
总结一下:
右左双旋的步骤如下

  1. 首先因为不平衡那个方向的子树的反方向偏重,呈折现状态,所以需要对其右旋转,让树恢复到直线状态
  2. 直线状态时就和一开始的单旋思路一样,按照单旋处理
  3. 调节平衡因子,根据subRL一开始的平衡因子进行调节,有两种情况,为-1时subR结束后为1,为1时parent结束后为-1。
//右左双旋
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子
	int bf = subRL->_bf;

	//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。
	RotateR(subR);
	//然后再左单旋即可
	RotateL(parent);

	//根据subRL的bf来调节旋转后的平衡因子
	if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_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 = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

插入

插入分为三个步骤

  1. 按照二叉搜索树的规则找到合适的位置插入
  2. 更新插入后的平衡因子
  3. 根据平衡因子来选择是否进行旋转调节。

思路前面已经讲了,这里直接复用代码就行,具体步骤也写在注释里

	bool Insert(const std::pair<K, V>& kv)
{
	//按照二叉搜索树的规则先找到位置
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	//插入节点
	cur = new Node(kv);

	//判断插入位置
	if (cur->_kv.first > parent->_kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	cur->_parent = parent;

	//更新平衡因子
	while (parent)
	{
		//更新父节点的平衡因子
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}

		//判断更新后父节点是否平衡

		//平衡
		if (parent->_bf == 0)
		{
			break;
		}
		//高度发生变化,要继续往上判断
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		//此时不平衡,需要旋转
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//旋转分四种情况,直线单旋,折线双旋

			if (parent->_bf == 2)
			{
				//如果右边不平衡,并且子节点也是右边偏重,则左单旋
				if (cur->_bf == 1)
				{
					RotateL(parent);
				}
				//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
				else
				{
					RotateRL(parent);
				}
			}
			else
			{
				//左边不平衡,并且子节点也是左边偏重,右单旋
				if (cur->_bf == -1)
				{
					RotateR(parent);
				}
				//同上,左右双旋
				else
				{
					RotateLR(parent);
				}
			}

			//旋转完后恢复平衡,更新结束。
			break;
		}
	}

	return true;
}

删除

AVL树的删除极为复杂,数据结构,算法导论这些书里仅仅只是提及了思路而没有实现。我自己实现了发现也大概要100多行,考虑的情况极为复杂。

虽然代码极为复杂,但是思路还是很简单,分为以下几步。

  1. 按照二叉搜索树的规则删除
  2. 更新平衡因子,并且进行旋转来调整(最坏情况下可能会一直调整到根节点)。

这里就直接复用上面平衡因子更新的代码以及之前博客实现的二叉搜索树的删除(博客链接在开始处),将其合并处理即可。

思路之前写过,就不再单独提出来说,直接写在注释里。

bool erase(const K& key)
{
	//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可
	Node* cur = _root;
	Node* parent = cur;
	/*
		删除有三种情况,一种是删除叶子节点,可以直接删除
		第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树
		前两种情况可以合并处理

		第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构
		如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。
	*/
	//删除部分
	while (cur)
	{
		//找到删除的位置
		if (key > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树
			//处理只有右子树时					
			if (cur->_left == nullptr)
			{
				//如果当前节点为根节点,则让右子树成为新的根节点
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					//判断当前节点是他父节点的哪一个子树
					if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
					else
					{
						parent->_left = cur->_right;
					}
				}

				delete cur;
			}
			//处理只有左子树时	
			else if (cur->_right == nullptr)
			{
				//如果当前节点为根节点,则让左子树成为新的根节点
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_right == cur)
					{
						parent->_right = cur->_left;
					}
					else
					{
						parent->_left = cur->_left;
					}
				}

				delete cur;
			}
			//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点
			else
			{
				//这里我选取的是左子树的最右节点

				Node* LeftMax = cur->_left;
				Node* LeftMaxParent = cur;

				//找到左子树的最右节点
				while (LeftMax->_right)
				{
					LeftMaxParent = LeftMax;
					LeftMax = LeftMax->_right;
				}

				//替换节点
				std::swap(cur->_kv, LeftMax->_kv);

				//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树
				//并且删除最右节点
				if (LeftMax == LeftMaxParent->_left)
				{
					LeftMaxParent->_left = LeftMax->_left;
				}
				else
				{
					LeftMaxParent->_right = LeftMax->_left;
				}

				delete LeftMax;
			}

			//删除成功,中断
			break;
		}
	}

	//查找不到
	if (cur == nullptr)
		return false;

	//更新平衡因子
	while (parent)
	{
		//更新父节点的平衡因子,注意这里和插入是反过来的,因为是删除
		if (cur == parent->_left)
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}

		//判断更新后父节点是否平衡

		//平衡
		if (parent->_bf == 0)
		{
			break;
		}
		//高度发生变化,要继续往上判断
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		//此时不平衡,需要旋转
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//旋转分四种情况,直线单旋,折线双旋

			if (parent->_bf == 2)
			{
				//如果右边不平衡,并且子节点也是右边偏重,则左单旋
				if (cur->_bf == 1)
				{
					RotateL(parent);
				}
				//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
				else
				{
					RotateRL(parent);
				}
			}
			else
			{
				//左边不平衡,并且子节点也是左边偏重,右单旋
				if (cur->_bf == -1)
				{
					RotateR(parent);
				}
				//同上,左右双旋
				else
				{
					RotateLR(parent);
				}
			}

			//旋转完后恢复平衡,更新结束。
			break;
		}
	}

	return true;
}

AVL树的验证

如要验证AVL树,有两个方法。
一是判断其是否具有二叉搜索树的特性,可以通过中序遍历来看看是否有序来判断。
二是通过判断他所有的子树是否两边高度平衡,来判断其是否具有平衡的特性。

中序遍历

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

			_InOrderTravel(root->_left);

			std::cout << root->_kv.first << ':'  << root->_kv.second << std::endl;

			_InOrderTravel(root->_right);
		}

		void InOrderTravel() const
		{
			_InOrderTravel(_root);
		}

测试代码

int main()
{
	lee::AVLTree<int, string> tree;
	tree.Insert(make_pair(3, "php"));
	tree.Insert(make_pair(1, "c++"));
	tree.Insert(make_pair(2, "c#"));
	tree.Insert(make_pair(7, "go"));
	tree.Insert(make_pair(11, "js"));
	tree.Insert(make_pair(19, "lua"));
	tree.Insert(make_pair(5, "sql"));
	tree.Insert(make_pair(8, "java"));
	tree.Insert(make_pair(4, "python"));

	tree.InOrderTravel();

	return 0;
}

在这里插入图片描述
这一部分测试是没有问题的。


平衡判断

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

			int leftHeight = countHeight(root->_left);
			int rightHeight = countHeight(root->_right);

			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		bool _IsBalance(Node* root) const
		{
			if (root == nullptr)
				return true;

			int leftHeight = countHeight(root->_left);
			int rightHeight = countHeight(root->_right);

			return abs(leftHeight - rightHeight) < 2
				&& _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

		bool IsBalance() const
		{
			return _IsBalance(_root);
		}

测试代码

int main()
{
	lee::AVLTree<int, string> tree;
	tree.Insert(make_pair(3, "php"));
	tree.Insert(make_pair(1, "c++"));
	tree.Insert(make_pair(2, "c#"));
	tree.Insert(make_pair(7, "go"));
	tree.Insert(make_pair(11, "js"));
	tree.Insert(make_pair(19, "lua"));
	tree.Insert(make_pair(5, "sql"));
	tree.Insert(make_pair(8, "java"));
	tree.Insert(make_pair(4, "python"));

	cout << tree.IsBalance();

	return 0;
}

在这里插入图片描述
可以看到,这个特性也满足。说明这棵树的实现没有问题。


AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度,即 log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,
但一个结构经常修改,就不太适合。


完整代码实现

#pragma once
#include<iostream>

namespace lee
{
	template<class K, class V>
	struct AVLTreeNode
	{
		AVLTreeNode(const std::pair<K, V>& kv)
			: _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _kv(kv)
			, _bf(0)
		{}

		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		std::pair<K, V> _kv;
		int _bf;
	};

	template<class K, class V>
	class AVLTree
	{
	public:
		typedef AVLTreeNode<K, V> Node;

		AVLTree() : _root(nullptr)
		{}

		~AVLTree()
		{
			destory(_root);
		}

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

			parent->_left = subLR;

			//如果subLR存在,则让他的父节点指向parent。
			if (subLR)
			{
				subLR->_parent = parent;
			}

			subL->_right = parent;

			Node* ppNode = parent->_parent;
			parent->_parent = subL;

			//两种情况
			//如果parent为根节点,则让subL成为新的根节点
			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			//如果不是根节点,则改变subL与其祖父节点的指向关系
			else
			{
				if (ppNode->_left == parent)
				{
					ppNode->_left = subL;
				}
				else
				{
					ppNode->_right = subL;
				}

				subL->_parent = ppNode;
			}

			//左旋完成后平衡,平衡因子归零。
			subL->_bf = parent->_bf = 0;
		}

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

			parent->_right = subRL;

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

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

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

				subR->_parent = ppNode;
			}

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

		//右左双旋
		void RotateRL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;
			//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子
			int bf = subRL->_bf;

			//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。
			RotateR(subR);
			//然后再左单旋即可
			RotateL(parent);

			//根据subRL的bf来调节旋转后的平衡因子
			if (bf == 1)
			{
				parent->_bf = -1;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else
			{
				parent->_bf = 0;
				subR->_bf = 0;
				subRL->_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 = 0;
				subL->_bf = -1;
				subLR->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 1;
				subL->_bf = 0;
				subLR->_bf = 0;
			}
			else
			{
				parent->_bf = 0;
				subL->_bf = 0;
				subLR->_bf = 0;
			}
		}

		bool Insert(const std::pair<K, V>& kv)
		{
			//按照二叉搜索树的规则先找到位置
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			//插入节点
			cur = new Node(kv);

			//判断插入位置
			if (cur->_kv.first > parent->_kv.first)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			cur->_parent = parent;

			//更新平衡因子
			while (parent)
			{
				//更新父节点的平衡因子
				if (cur == parent->_left)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

				//判断更新后父节点是否平衡
				
				//平衡
				if (parent->_bf == 0)
				{
					break;
				}
				//高度发生变化,要继续往上判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_parent;
				}
				//此时不平衡,需要旋转
				else if (parent->_bf == 2 || parent->_bf == -2)
				{
					//旋转分四种情况,直线单旋,折线双旋

					if (parent->_bf == 2)
					{
						//如果右边不平衡,并且子节点也是右边偏重,则左单旋
						if (cur->_bf == 1)
						{
							RotateL(parent);
						}
						//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
						else
						{
							RotateRL(parent);
						}
					}
					else
					{
						//左边不平衡,并且子节点也是左边偏重,右单旋
						if (cur->_bf == -1)
						{
							RotateR(parent);
						}
						//同上,左右双旋
						else
						{
							RotateLR(parent);
						}
					}

					//旋转完后恢复平衡,更新结束。
					break;
				}
			}

			return true;
		}

		Node* Find(const K& key)
		{
			//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树
			Node* cur = _root;

			while (cur)
			{
				//比根节点大则查找右子树
				if (key > cur->_kv.first)
				{
					cur = cur->_right;
				}
				//比根节点小则查找左子树
				else if (key < cur->_kv.first)
				{
					cur = cur->_left;
				}
				//相同则返回
				else
				{
					return cur;
				}
			}

			//遍历完则说明查找不到,返回false
			return nullptr;
		}

		bool erase(const K& key)
		{
			//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可
			Node* cur = _root;
			Node* parent = cur;

			
			/*
				删除有三种情况,一种是删除叶子节点,可以直接删除
				第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树
				前两种情况可以合并处理

				第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构
				如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。
			*/
			//删除部分
			while (cur)
			{
				//找到删除的位置
				if (key > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (key < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树
					//处理只有右子树时					
					if (cur->_left == nullptr)
					{
						//如果当前节点为根节点,则让右子树成为新的根节点
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							//判断当前节点是他父节点的哪一个子树
							if (parent->_right == cur)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}

						delete cur;
					}
					//处理只有左子树时	
					else if (cur->_right == nullptr)
					{
						//如果当前节点为根节点,则让左子树成为新的根节点
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_right == cur)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = cur->_left;
							}
						}

						delete cur;
					}
					//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点
					else
					{
						//这里我选取的是左子树的最右节点

						Node* LeftMax = cur->_left;
						Node* LeftMaxParent = cur;

						//找到左子树的最右节点
						while (LeftMax->_right)
						{
							LeftMaxParent = LeftMax;
							LeftMax = LeftMax->_right;
						}

						//替换节点
						std::swap(cur->_kv, LeftMax->_kv);

						//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树
						//并且删除最右节点
						if (LeftMax == LeftMaxParent->_left)
						{
							LeftMaxParent->_left = LeftMax->_left;
						}
						else
						{
							LeftMaxParent->_right = LeftMax->_left;
						}

						delete LeftMax;
					}

					//删除成功,中断
					break;
				}
			}

			//查找不到
			if (cur == nullptr)
				return false;

			//更新平衡因子
			while (parent)
			{
				//更新父节点的平衡因子
				if (cur == parent->_left)
				{
					parent->_bf++;
				}
				else
				{
					parent->_bf--;
				}

				//判断更新后父节点是否平衡

				//平衡
				if (parent->_bf == 0)
				{
					break;
				}
				//高度发生变化,要继续往上判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_parent;
				}
				//此时不平衡,需要旋转
				else if (parent->_bf == 2 || parent->_bf == -2)
				{
					//旋转分四种情况,直线单旋,折线双旋

					if (parent->_bf == 2)
					{
						//如果右边不平衡,并且子节点也是右边偏重,则左单旋
						if (cur->_bf == 1)
						{
							RotateL(parent);
						}
						//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
						else
						{
							RotateRL(parent);
						}
					}
					else
					{
						//左边不平衡,并且子节点也是左边偏重,右单旋
						if (cur->_bf == -1)
						{
							RotateR(parent);
						}
						//同上,左右双旋
						else
						{
							RotateLR(parent);
						}
					}

					//旋转完后恢复平衡,更新结束。
					break;
				}
			}

			return true;
		}

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

			_InOrderTravel(root->_left);

			std::cout << root->_kv.first << ':'  << root->_kv.second << std::endl;

			_InOrderTravel(root->_right);
		}

		void InOrderTravel() const
		{
			_InOrderTravel(_root);
		}

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

			int leftHeight = countHeight(root->_left);
			int rightHeight = countHeight(root->_right);

			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		bool _IsBalance(Node* root) const
		{
			if (root == nullptr)
				return true;

			int leftHeight = countHeight(root->_left);
			int rightHeight = countHeight(root->_right);

			return abs(leftHeight - rightHeight) < 2
				&& _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

		bool IsBalance() const
		{
			return _IsBalance(_root);
		}

		void destory(Node*& root)
		{
			Node* node = root;
			if (!root)
				return;

			destory(node->_left);
			destory(node->_right);

			delete node;
			node = nullptr;
		}
	private:
		Node* _root;
	};
};

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/106993258