【C++】AVL树

一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查

找元素相当于在顺序表中搜索元素,效率低下。从原来的时间复杂度O(logn)转变为O(n)

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树 高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/1/0)

二、AVL树节点的定义

AVL的概念:左右子树高度差不超过1

因此要在二叉搜索树上增加一个平衡因子(来记录左右子树的高度差),当平衡因子超过1时,就要更新向上调整父节点的平衡因子(因为子节点的平衡因子发生变化,间接的导致父节点的平衡因子发生改变),这时就要知道父节点的位置,因此在二叉搜索树上要多维护一个父节点的指针。

复制代码
struct AVLTreeNode
{
	pair<K, V> _key;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance fatcor
	AVLTreeNode(const pair<K, V>& key)
		: _key(key)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

三、AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子

  1. 按照二叉搜索树的方式插入新节点

  2. 调整节点的平衡因子

下面是二叉搜索树的插入,这个树的结构是左子树小于根,右子树大于根。

复制代码
	bool Insert(const pair<K, V>& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key.first < key.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key.first > key.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key.first < key.first)
		{
			parent->_right = cur;
		}
		else {
			parent->_left = cur;
		}
}

调整节点的平衡因子

插入新节点,会影响部分祖先节点的平衡因子,因此要不断的更新平衡因子。

插入在左子树,parent平衡因子--

插入在右子树,parent平衡因子++

1、parent的平衡因子 == 0

parent的平衡因子更新前是 1 or -1,插入节点插入在矮的那边。parent所在子树的高度不变,不需要继续往上更新

2、parent的平衡因子 == 1 / -1

说明parent的平衡因子更新前是0,插入节点插入在任意一边。parent所在的子树高度都会变化了,需要继续往上更新

3、parent的平衡因子 == 2 / -2

说明parent的平衡因子更新前是1 / -1,插入节点插入在高的那边,进一步加剧了parent所在子树的不平衡,所以要发生旋转,旋转后要更新平衡因子

复制代码
		// 因为因为父节点要连接一下父节点的位置
        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)
			{
				//旋转处理
			}
			else {
				assert(false);
			}
		}

3.1 AVL左单旋

上面图中的h可以认为是抽象图,就是二叉树搜索树的分支。

上面的图可以认为在subR的右子树(记住是右子树)中任意插入一个节点,使右子树的高度+1,导致subR的平衡因子发生改变,从而影响parent的平衡因子变成了2,从而发生旋转。发生左旋转的条件是什么?是parent的平衡因子为2,subR的平衡因子为1时发生左旋转。

代码实现一下

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

		if (subRL)
			subRL->_parent = parent;
		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (parentParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else {
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else {
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
		parent->_bf = subR->_bf = 0;
	}

3.2 AVL右单旋

subL为parent的左子树,subLR为subL的右子树,当a这个位置高度变为h+1时,parent平衡因子为-2,subL平衡因子为-1时,此时发生右旋转,parent的左子树变为subLR,subL变为父节点且右子树为parent

代码实现一下

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

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

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

3. 2 左右双旋

左旋

如果subRL这个二叉树中高度增加1时也就是说当parent的平衡因子为2,subR的平衡因子为-1时左单旋,我们不难发现旋转的结果与预期不符

右旋如果subLR这个二叉树高度增加1时也就是说parent的平衡因子为-2,subL的平衡因子为1时进行右单旋,旋转的结果与预期不符。旋来旋去发现高度没有变化。

如何解决parent的平衡因子为2,subR的平衡因子为-1的问题?

上图就是解决方式,增加节点后的第二个图,90为parent进行右旋到第三张图,之后30再变成parent进行左旋。

这里最麻烦的是平衡因子的维护

当h == 0时

也就是说subRL这个节点为新增节点,subRL与parent与subR平衡因子都为0。

当h > 0 时

因为增加节点有可能在b或者在c,不能确定parent这个位置平衡因子是-1/0还是subR这个位置的平衡因子是0/1,如何解决呢?

我们记录一下插入节点后subRL的平衡因子,如果subRL的平衡因子为1,经过左右双旋后,parent平衡因子为-1,subR为0,subRL为0。如果subRL的平衡因子为-1,经过左右双旋后,parent的平衡因子为0,subR为1,subRL为0。

代码实现

复制代码
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 0)
		{
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

如何解决parent的平衡因子为-2,subR的平衡因子为1的问题?

上图就是解决方式,增加节点后的第二个图,30为parent进行左旋到第三张图,之后90再变成parent进行右旋。

平衡因子的维护

这里也要分三种情况来进行判断

当h == 0时

subRL为新插入节点时,parent的平衡因子与subL和subLR都为0

当h > 0时

新增节点插入b位置时,subLR的平衡因子为-1时,双旋后parent的平衡因子为1,subL和subLR的平衡因子为0。

新增节点插入c位置时,subLR的平衡因子为1时,双旋后parent的平衡因子为0,subL的平衡因子为-1,subLR的平衡因子为0。

代码实现

复制代码
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);
		if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else {
			assert(false);
		}
	}
相关推荐
Jerry2 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry3 小时前
LeetCode 459. 重复的子字符串
算法
海石6 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石6 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry8 小时前
LeetCode 151. 反转字符串中的单词
算法
a11177611 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜11 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry11 小时前
LeetCode 541. 反转字符串 II
算法
Jerry11 小时前
LeetCode 344. 反转字符串
算法
搞科研的小刘选手11 小时前
【香港大学主办&IEEE出版】第六届计算机视觉、应用与算法国际学术会议(CVAA 2026)
算法·计算机视觉·应用·学术会议