【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);
		}
	}
相关推荐
盼海1 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
Swift社区8 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman8 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年9 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Dong雨9 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
SoraLuna9 小时前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
liujjjiyun10 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥10 小时前
c++中mystring运算符重载
开发语言·c++·算法
trueEve11 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展