AVL树(C++详解版)

1. AVL的概念

  • AVL是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平衡。
  • AVL树实现这里我们引入一个平衡因子的概念,每个结点都有⼀个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进性观察和控制树是否平衡。
  • AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在 log⁡n\log nlogn,那么增删查改的效率也可以控制在Olog⁡(n)\log (n)log(n) ,相比与二叉搜索树有了本质的提升。

2.AVL的实现

2.1 AVL树的结构

cpp 复制代码
template<class K,class V>
struct AVLNode
{
	pair<K, V> _kv;
	AVLNode<K, V>* _parent;
	AVLNode<K, V>* _left;
	AVLNode<K, V>* _right;
	int _bf;
	AVLNode(const pair<K,V>&kv)
		:_kv(kv)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_bf(0)
	{ }
};

template<class K,class V>
class AVLTree
{
	using Node = AVLNode<K, V>;
public:

private:
	Node* _root = nullptr;
};

2.2 AVL树的插入

    1. 插⼊⼀个值按⼆叉搜索树规则进行。
    1. 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。
    1. 更新平衡因子过程中没有出现问题,则插入结束
    1. 更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,本质降低了子树的高度,不会再影响上一层,所以插入结束。

平衡因子更新

更新原则:

  • 平衡因子 = 右子树高度-左子树高度
  • 只有子树高度变化才会影响当前结点平衡因子。
  • 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在parent的左子树,parent平衡因子--
  • parent所在⼦树的⾼度是否变化决定了是否会继续往上更新

更新停止条件:

  • 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0 或者 1->0,说明更新前parent子树⼀边高⼀边低,新增的结点插⼊在低的那边,插入后parent所在子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束。
  • 更新后parent的平衡因子等于1 或 -1,更新前更新中parent的平衡因子变化为0->1 或者 0->-1,说明更新前parent子树两边⼀样高,新增的插入结点后,parent所在的子树⼀边高⼀边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新。
  • 更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因子变化为1->2 或者 -1->-2,说明更新前parent子树⼀边高⼀边低,新增的插入结点在高的那边,parent所在的子树高的那边更高了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:
    1、把parent子树旋转平衡。
    2、降低parent子树的高度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插入结束。
  • 不断更新,更新到根,根的平衡因子是1或-1也停止了。

    插入代码实现
cpp 复制代码
bool Insert(const pair<K,V>&kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	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->_left = cur;
	}
	else
	{
		parent->_right = 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 = cur->_parent;
		}
		else if (parent->_bf == 2||parent->_bf == -2)
		{
			//旋转
		}
		else
		{
			assert(false);
		}
	}
}

3.AVL树的旋转

旋转的原则

    1. 保持搜索树的规则
    1. 让旋转的树从不满足变平衡,其次降低旋转树的高度旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

1.右单旋

当左子树纯粹地比右边高时,且高度差为2时,就要开始右单旋,右单旋有一个好记的规则,就是将旋转点的左子树的右子树放到右子树的左子树,可以参考下面的例子。

而且有时候要旋转两次,一次时不够的,图三就是旋转了2次。

代码实现

cpp 复制代码
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	Node* Pparent = parent->_parent;
	parent->_parent = subL;
	subL->_right = parent;
	if (Pparent==nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (Pparent->_left == parent)
		{
			Pparent->_left = subL;
		}
		else
		{
			Pparent->_right = subL;
		}
		subL->_parent = Pparent;
	}
	parent->_bf = 0;
	subL->_bf = 0;
}

2.左单旋

左单旋和右单旋同理,都是规则是一样的,唯一不同的就是,左单旋是将根的右子树的左子树放到根的左子树的右子树

代码实现:

cpp 复制代码
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	parent->_parent = subR;
	subR->_left = parent;
	Node* Pparent = parent->_parent;
	if (Pparent == nullptr)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == Pparent->_left)
		{
			Pparent->_left = subR;
		}
		else
		{
			Pparent->_right = subR;
		}
		subR->_parent = Pparent;
	}
	parent->_bf = 0;
	subR->_bf = 0;
}

3.右左双旋

产生双旋的条件是比较好辩别的,就是当一棵树不是纯粹的一边高时,就会发生双旋,比如图一,根的右子树比左子树高,本来理应左旋,但是左子树的右子树比左子树矮,左旋的代价是把旋上去的那个节点的左子树放到原本的根的右子树上,这样就会陷入循环,导致问题无法解决,所以我们要把右子树变成纯粹的一边高

情景有以下几种:

代码演示:

cpp 复制代码
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);
	if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);     
	}
}

4.左右双旋

原理和右左双旋一样

代码演示:

cpp 复制代码
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);
	if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);     
	}
}

4.AVL树的平衡检测

AVL树的平衡检测需要检查检查平衡因子是否正确,这时候我们需要一个参照物,就要借助树的高度来检查平衡因子是否没有出错。

而树的高度我们需要用到递归的方式来得到:

代码演示:

cpp 复制代码
int Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int HeightLeft = Height(root->_left);
	int HeightRight = Height(root->_right);
	return HeightLeft > HeightRight ? HeightLeft + 1: HeightRight + 1;
}
bool IsBalanceTree(Node*root)
{
	if (root == nullptr)
	{
		return true;
	}
	int Heightleft = Height(root->_left);
	int Heightright = Height(root->_right);
	int bf = Heightleft - Heightright;
	if (bf == -2 || bf == 2)
	{
		return false;
	}
	if (bf != root->_bf)
	{
		return false;
	}
	return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
}
相关推荐
思麟呀1 小时前
C++工业级日志项目(七)日志器核心
linux·开发语言·c++·windows
郝学胜_神的一滴1 小时前
Qt 高级开发 019:从零定制登录窗口按钮、Logo 样式与交互悬浮效果
c++·qt
lcj25111 小时前
vector的基本使用 + 手搓成员变量 size capacity begin end operator[] reserve扩容 拷贝构造 赋值析构
开发语言·c++·笔记·面试
liulilittle2 小时前
C++ do_div 宏
c++
-To be number.wan2 小时前
算法日记 | STL-MAP
c++·算法
cjp5602 小时前
015. UG 二次开发,拉伸草图生成实体类,高级草图类封装
算法
楼田莉子2 小时前
C++20新特性:Range库
开发语言·c++·后端·学习·c++20
字节高级特工2 小时前
【Linux】深入理解C语言命令行参数与环境变量
linux·c++·人工智能·后端
linux开发之路2 小时前
C++项目推荐:eBPF+调度器性能分析框架
linux·c++·ebpf·火焰图·调度器