AVL树

AVL树的引入是为了解决二叉树搜索树的极端情况的。当二叉搜索树插入一个有序的数组,那么就会退化为一颗单边树。

目录

AVL树的结点定义

AVL树的插入​​​​​​​

 左单旋

 右单旋

左右双旋

 右左双旋

 AVL树插入的整体代码

AVL树的验证 

AVL树的删除

AVL树的删除代码实现


AVL树的前提是一颗二叉搜索树,只不过需要所有子树的左右高度差不超过1.

AVL树的结点定义

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf;  // balance factor 平衡因子
	pair<K, V> _kv;

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

AVL树的插入

AVL树就是在二叉搜索树的基础上新增了平衡因子平衡因子 = 右子树高度 - 左子树高 ( |平衡因子| <= 1) ),和一个父结点指针
AVL树的插入分为两步:

  1. 按照二叉搜索树的规则插入
  2. 插入之后更新平衡因子,根据平衡因子去维持AVL树的性质
  •  按照二叉搜索树的插入规则插入一个结点(cur),那么会有一个父结点(parent),这样才能知道插入的是parent的左边还是右边。
  • 平衡因子的引入就是为了平衡二叉树的每个结点的左右子树高度差不超过1
  • 一个结点的插入,首先就会影响该结点的父结点的平衡因子,然后再不断向上更新结点的平衡因子

根据平衡因子去分析插入结点之后什么时候更新平衡因子结束,什么时候需要旋转

1.cur为新插入的结点

  • 如果cur是parent的左,parent->bf--
  • 如果cur是parent的右,parent->bf++

2.更新完parent->bf,如果parent->bf == 0,说明parent的高度不变,更新结束,插入完成

  • 解释:说明更新前,parent->bf == 1 or -1,现在变成0,说明把矮的那边填上了,说明我的高度不变,对上层没有影响。

3.更新完parent->bf,如果parent->bf == 1 or -1,说明parent的高度变了,继续往上更新

  • 解释:说明更新前,parent->bf == 0(注意这里不能是2 or -2,因为在插入前是2 or -2,那么就已经是一颗不平衡的二叉树了),现在变成1 or -1,说明变高了,对上层有影响

 对于第3点的图解:

4.更新完parent->bf,如果parent->bf == 2 or -2,说明parent所在的子树出现了不平衡,需要旋转处理

  • 旋转完成后,还得是搜索树
  • 旋转完成后,变平衡

旋转分类:
旋转分为左单旋(RotateL)、右单旋(RotateR)、左右双旋(RotateLR)、右左双旋(RotateRL)
这些旋转的旋转是根据平衡因子来选择的,具体的选择情况都在下面的表格中

parent->_bf == 2
cur->bf == 1RotateL(parent)
cur->bf == -1RotateRL(parent)
parent->_bf == -2
cur->bf == 1RotateLR(parent)
cur->bf == -1RotateR(parent)

 左单旋

void RotateL(Node* parent)
{
	// 注意:链接关系一对一对去断开和连接,这样不容易混乱,更加有条理
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	if(subRL)
		subRL->_parent = parent;
	subR->_left = parent;
	// 假如parent只是一颗子树的根结点,那么需要记录一下parent的父结点
	Node* ppNode = parent->_parent;
	parent->_parent = subR;
	// 1、原来parent是这颗树的根,现在subR是根
	// 2、parent为根的树只是整颗树的子树,改变链接关系,subR顶替它的位置
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		subR->_parent = ppNode;
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;
	}
	parent->_bf = subR->_bf = 0;
}

 右单旋

void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if(subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	Node* ppNode = parent->_parent;
	parent->_parent = subL;
	if (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		subL->_parent = ppNode;
		if (ppNode->_left == parent)
			ppNode->_left = subL;
		else
			ppNode->_right = subL;
	}
	parent->_bf = subL->_bf = 0;
}

注意:parent可能是一颗子树的根结点,也可能是整颗树的根结点,对于这种情况需要特判。同时不要忘记AVL树是三叉链,需要修改_parent。注意结点之间的链接关系、链接顺序,一对一对进行拆分和链接,不容易搞混。最后左单旋和右单旋都是将parent和cur的平衡因子更新为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 if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

 右左双旋

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 = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		parent->_bf = -1;
		subRL->_bf = 0;
	}
	else if (bf == 0)
	{
		subR->_bf = 0;
		parent->_bf = 0;
		subRL->_bf = 0;
	}
}

注意:双旋的难点注意是在旋转完之后的更新平衡因子上,旋转过程就是调用左单旋和右单旋的接口。这里需要注意的是第一次的旋转对于parent的孩子结点旋转,至于是左孩子还是右孩子,画图出来就很清楚了。最后是更新平衡因子,平衡因子的更新是根据末尾结点的平衡因子进行讨论的,也就是subRL、subLR,最后给平衡因子赋值,也是需要画图的,强记是不现实的。

 AVL树插入的整体代码

bool Insert(const pair<K, V>& kv)
{
	//1、先按二叉搜索树的规则进行插入
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	//更新平衡因子
	while (parent)
	{
		if (cur == parent->_right)
			parent->_bf++;
		else
			parent->_bf--;
		if (parent->_bf == 0)
			// 说明parent所在的子树高度不变,更新结束
			break;
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			// 说明parent所在的子树的高度变了,继续往上更新
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//parent所在的子树出现不平衡了,需要旋转处理
			// 旋转的目的是维持插入结点后的树仍然是二叉搜索树

			if (parent->_bf == 2)
			{
				if (cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (cur->_bf == -1)
				{
					// 双旋
					RotateRL(parent);
				}
			}
			else if (parent->_bf == -2)
			{
				if (cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (cur->_bf == 1)
				{
					// 双旋
					RotateLR(parent);
				}
			}
			// 旋转完成后,parent所在的树的高度恢复到插入前
			// 如果是子树,对上层没有影响,更新结束
			break;
		}
	}
	return true;
}
// 左单旋
void RotateL(Node* parent)
{
	// 注意:链接关系一对一对去断开和连接,这样不容易混乱,更加有条理
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	if(subRL)
		subRL->_parent = parent;
	subR->_left = parent;
	// 假如parent只是一颗子树的根结点,那么需要记录一下parent的父结点
	Node* ppNode = parent->_parent;
	parent->_parent = subR;
	// 1、原来parent是这颗树的根,现在subR是根
	// 2、parent为根的树只是整颗树的子树,改变链接关系,subR顶替它的位置
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		subR->_parent = ppNode;
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;
	}
	parent->_bf = subR->_bf = 0;
}
// 右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if(subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	Node* ppNode = parent->_parent;
	parent->_parent = subL;
	if (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		subL->_parent = ppNode;
		if (ppNode->_left == parent)
			ppNode->_left = subL;
		else
			ppNode->_right = subL;
	}
	parent->_bf = subL->_bf = 0;
}

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 = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		parent->_bf = -1;
		subRL->_bf = 0;
	}
	else if (bf == 0)
	{
		subR->_bf = 0;
		parent->_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 if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

AVL树的验证 

1、验证它的中序遍历是否有序
2、每个结点的高左右度差不超过1

void _Inorder(Node* root)
{
	if (root == nullptr)
		return;
	_Inorder(root->_left);
	cout << (root->_kv).first << ' ' << (root->_kv).second << endl;
	_Inorder(root->_right);
}
void Inorder()
{
	_Inorder(_root);
}
int Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	return abs(leftHeight - rightHeight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

AVL树的删除

删除其实和插入的讨论方法是一样的。

删除也分为两大步:

  1. 按照二叉搜索树的规则进行删除
  2. 删除之后更新平衡因子,根据平衡因子去维持AVL树的性质

第一步就不过多阐述了。需要注意的是,我们找到要删除的结点之后,不要先急于删除,先记录下来,更新完平衡因子之后,再删除。这样会方便一点

删除操作的更新平衡因子和插入操作是相反的:

删除的结点为parent的左孩子parent->bf++
删除的结点为parent的右孩子parent->bf--

每更新一次平衡因子,还需要判断更新是否结束,是否需要旋转

parent->bf == 0高度降低了,需要向上更新
parent->bf == 1 || parent->bf == -1高度不变,结束更新
parent->bf == 2 || parent->bf == -2左右子树高度差超过1,需要旋转处理

 旋转分类:(和插入一模一样)

parent->bf == -2
cur->bf == 1RotateLR(parent)
cur->bf == -1RotateR(parent)

具体的图解就不画了,和插入操作是一样的,大家可以参考上面的插入的旋转操作

AVL树的删除代码实现

bool Erase(const pair<K, V>& kv)
{
	int flag = 0;
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			// 找到要删除的结点了
			// 如果要删除的结点为根结点,可以直接删除
			// 其他情况,先平衡因子,再删除
			// 假如先删除的话,那么就不知道是父结点的左结点被删除,还是右结点被删除
			// 这样就不能选择那种旋转了
			flag = 1;
			if (cur->_left == nullptr)
			{
				if (_root == cur)
				{
					_root = _root->_right;
					if (_root)
					{
						_root->_parent = nullptr;
					}
					delete cur;
					return true;
				}
				else
				{
					break;
				}
			}
			else if (cur->_right == nullptr)
			{
				if (_root == cur)
				{
					_root = _root->_left;
					if (_root)
						_root->_parent = nullptr;
					delete cur;
					return true;
				}
				else
				{
					break;
				}
			}
			// 要删除的结点左右都不为空
			else
			{
				Node* rightMinParent = cur;
				Node* rightMin = cur->_right;
				while (rightMin->_left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}
				//替代
				cur->_kv.first = rightMin->_kv.first;
				cur->_kv.second = rightMin->_kv.second;
				cur = rightMin;
				parent = rightMinParent;
				break;
			}
		}
	}
	if (flag == 0)
		return false;
	Node* delNode = cur;  // 记录要删除的结点
	Node* delNodeParent = parent; // 要删除结点的父结点
	// 平衡因子
	while (parent)
	{
		if (cur == parent->_left)
			parent->_bf++;
		else
			parent->_bf--;
		if (parent->_bf == -1 || parent->_bf == 1)
			break;
		// 父结点平衡因子等于0
		else if (parent->_bf == 0)
		{
			// 向上调整
			cur = parent;
			parent = parent->_parent;
		}
		// 需要旋转处理
		else if (parent->_bf == -2 || parent->_bf == 2)
		{
			if (parent->_bf == -2)
			{
				if (cur->_bf == 1)
					RotateLR(parent);
				else if (cur->_bf == -1)
					RotateR(parent);
			}
			else
			{
				if (cur->_bf == 1)
					RotateL(parent);
				else if (cur->_bf == -1)
					RotateRL(parent);
			}
			break;
		}
	}
	if (delNode == nullptr)
		return true;
	// 删除结点
	if (delNode->_left == nullptr && delNode->_right == nullptr)
	{
		if (delNode == delNodeParent->_left)
			delNodeParent->_left = nullptr;
		else
			delNodeParent->_right = nullptr;
	}
	else if (delNode->_left == nullptr)
	{
		if (delNode == delNodeParent->_left)
			delNodeParent->_left = delNode->_right;
		else
			delNodeParent->_right= delNode->_right;
		delNode->_right->_parent = delNodeParent;
	}
	else if (delNode->_right == nullptr)
	{
		if (delNode == delNodeParent->_left)
			delNodeParent->_left = delNode->_left;
		else
			delNodeParent->_right = delNode->_left;
		delNode->_left->_parent = delNodeParent;
	}
	delete delNode;
	return true;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值