【C++】AVL树

AVL树

1、AVL树的概念

AVL树是最先发明的自平衡⼆叉查找树,AVL是一颗空树,或者具备下列性质的二叉搜索树:**它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。**AVL树是⼀颗高度平衡搜索二叉树,通过控制高度差去控制平衡

如果一棵二叉搜索树是高度平衡的,它就是AVL树,AVL树整体节点数量和分布和完全二叉树类似,高度可以控制在 l o g N logN logN,那么增删查改的效率也可

以控制在 O ( l o g N ) O(logN) O(logN),相比二叉搜索树有了本质的提升。

2、AVL树节点的定义

cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到
	pair<K, V> _kv;  //存储key和value
	AVLTreeNode<K, V>* _left; //指向左孩子
	AVLTreeNode<K, V>* _right; //指向右孩子
	AVLTreeNode<K, V>* _parent; //指向父亲结点
	int _bf; // balance factor  平衡因子

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

3、AVL树的整体结构

cpp 复制代码
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root = nullptr;
};

4、AVL树的插入

AVL树在二叉搜索树的基础上引入平衡因子,所以:

  • AVL树插入一个值按二叉搜索树规则进行插入
  • 调整节点的平衡因子

每个结点都有⼀个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡

4.1 平衡因子的更新

新节点插入后,AVL树的平衡性可能会遭到破坏,此时需要更新平衡因子,对AVL的平衡性进行检测

  • 平衡因子 = 右子树高度 - 左子树高度
  • 只有子树高度变化才会影响当前结点平衡因子
  • parent所在子树的高度是否变化决定了是否会继续往上更新
  • 如果节点插入到parent的左侧,只需要把parent的平衡因子 -1 即可
  • 如果节点插入到parent的右侧,只需要把parent的平衡因子 +1 即可

更新后parent的平衡因子有三种情况:

  1. 更新后parent的平衡因子等于0,说明插入前parent的平衡因子一定为+1或-1,说明更新前parent子树⼀边高⼀边低,新增的节点插入在低的一边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束
  2. 更新后parent的平衡因子是+1或-1,说明插入前的平衡因子是0,及插入前parent的子树两边一样高,新增的节点插入后parent所在的子树⼀边高一边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新
  3. 更新后parent的平衡因子等于2 或 -2,不符合平衡树的性质,需要进行旋转处理
cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	// 这棵树为空,建立根结点
	if (_root == nullptr)
	{
		//新建节点,存入要插入的键值对,赋值给根结点
		_root = new Node(kv);
		return true;
	}
	
	// 树不为空,循环寻找插入位置的父节点parent
	// parent初始置空
	Node* parent = nullptr;
	// cur遍历指针,从根节点开始向下遍历二叉搜索树
	Node* cur = _root;
	while (cur)
	{
		// 当前节点的key < 待插入key → 新节点要插在右子树
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		// 当前节点的key > 待插入key → 新节点要插在左子树
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		// 剩下情况:两个key相等,二叉搜索树不允许重复key,插入失败,返回false
		else
		{
			return false;
		}
	}

	// 循环结束后cur一定为空,parent为父节点
	// 复用cur,储存kv的节点
	cur = new Node(kv);
	cur->_bf = 0;  //平衡因子初始化为0

	// 判断新节点是父亲的右孩子还是左孩子
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	// 新节点和父节点相连接,旋转操作需要用到父指针
	cur->_parent = parent;

	// 从父节点向上遍历所有祖先,更新平衡因子,失衡则旋转
	// parent不为空,说明还有上层祖先需要更新bf,循环向上走
	while (parent)
	{
		// 判断新节点cur是parent的右孩子还是左孩子,更新bf
		if (cur == parent->_right)
		{
			// 新增右孩子 → 右子树高度+1,bf自增1
			parent->_bf++;
		}
		else
		{
			// 新增左孩子 → 左子树高度-1,bf自减1
			parent->_bf--;
		}
		
	//----------情况1:更新后平衡因子等于0--------------------
		// 插入前parent bf是±1,插入后左右等高
		if (parent->_bf == 0)
		{
			break;  //跳出循环,不用再向上更新
		}
		
	// ---------情况2:更新后平衡因子为1 / -1-----------------
		// 插入前parent bf是0,插入后子树高度+1,会影响上层祖先,必须继续向上更新
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			// 继续往上更新
			// 把当前parent赋值给cur:下一轮循环的"子节点"就是现在的父节点
			cur = parent;
			// parent向上跳一层,指向自己的父节点,下一轮循环处理更高层
			parent = parent->_parent;
		}
		
	// ----情况3:更新后平衡因子为2/-2,树失衡,需要旋转修复------
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 旋转处理
			// 右单旋:parent bf=-2,左孩子cur bf=-1 → 单右旋
			if (parent->_bf == -2 && cur->_bf == -1)
			{
				RotateR(parent);
			}
			// 左单旋:parent bf=2,右孩子cur bf=1 → 单左旋
			else if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(parent);
			}
			// 左右双旋:parent bf=-2,左孩子cur bf=1 → 先左后右双旋
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
			}
			// 右左双旋:parent bf=2,右孩子cur bf=-1 → 先右后左双旋
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			// 理论上不会走到这里,走到说明逻辑出错
			else
			{
				assert(false);
			}
			// 旋转修复后子树高度恢复原样,上层祖先不受影响,跳出循环
			break;
		}
		
		// 兜底分支:合法AVL树节点bf只能是-1/0/1,走到这里说明插入前树就已经失衡损坏
		else
		{
			// 插入之前这棵树就有2/-2 bf的节点,这棵树之前就不是AVL树
			assert(false);
		}
	}

	return true;
}

4.2 AVL树的旋转

旋转的原则:

  1. 保持搜索树的原则
  2. 让旋转的树从不平衡变平衡,其次降低旋转树的高度

旋转分为四种左单旋/右单旋/左右双旋/右左双旋

4.2.1 右单旋

新节点插入在左子树的左侧,左左失衡:即parent的平衡因子为-2,cur的平衡因子为-1,此时需要右单旋

旋转核心步骤,因为5<b子树的值<10,将b变成10的左子树,10变成5的右子树,5变成这棵树新的根,符合搜索树的规则,控制了平衡

cpp 复制代码
void RotateR(Node* parent)
{
	// 1. 取出parent的左孩子,命名subL
	Node* subL = parent->_left;
	// 2. 取出subL的右子树,命名subLR
	Node* subLR = subL->_right;

	// 第一步:断开subL和subLR,把subLR挂到parent左边
	parent->_left = subLR;
	// 如果subLR不是空树,就要反向设置它的父节点为parent(双向指针)
	if (subLR)
		subLR->_parent = parent;

	// 3. 提前保存parent原来的父节点(上层节点,后面要让上层指向新根subL)
	Node* parentParent = parent->_parent;

	// 第二步:subL和parent互相连接,subL当parent的父节点
	subL->_right = parent;
	parent->_parent = subL;

	//情况1:失衡节点parent是整棵树的根节点
	if (parent == _root)
	{
		//整棵树的根更新为subL
		_root = subL;
		// 根节点无父节点,置空父节点
		subL->_parent = nullptr;
	}

	// 情况2:parent不是根,存在上层父节点parentParent
	else
	{
		// 如果parent是上层节点的左孩子
		if (parentParent->_left == parent)
		{
			// 上层左分支改为指向新根subL
			parentParent->_left = subL;
		}
		// 如果parent是上层节点的右孩子
		else
		{
			// 上层右分支改为指向新根subL
			parentParent->_right = subL;
		}
		// 新根subL的父指针绑定上层节点parentParent
		subL->_parent = parentParent;
	}
	// 单右旋完成后,subL与parent两棵节点平衡因子都变为0
	parent->_bf = subL->_bf = 0;
}

4.2.2 左单旋

新节点插入在右子树的右侧,右右失衡:即parent的平衡因子为2,cur的平衡因子为1,此时需要左单旋

旋转核心步骤,因为10 < b子树的值 < 15,将b变成10的右子树,10变成15的左子树,15变成这棵树新的根,符合搜索树的规则,控制了平衡

cpp 复制代码
void RotateL(Node* parent)
{
	// 把parent的右孩子设置为subR
	Node* subR = parent->_right;
	// 拿到subR自己的左子树subRL,这棵树等下要挪给parent
	Node* subRL = subR->_left;

	// 把subRL挂到parent的右指针上
	parent->_right = subRL;
	// 如果subRL不是空树,要双向绑定
	if (subRL)
		subRL->_parent = parent;

	// 记录parent原来的父节点
	Node* parentParent = parent->_parent;

	// subR的左分支现在接parent,parent变成subR的左孩子
	subR->_left = parent;
	parent->_parent = subR;

	// 分支1:如果原来失衡节点parent是整棵AVL树的根
	if (parent == _root)
	{
		// 整棵树的根换成subR
		_root = subR;
		// 根节点没有父节点,父指针置空
		subR->_parent = nullptr;
	}
	// 分支2:parent不是全局根,上面还有父节点parentParent
	else
	{
		// 判断parent是上层节点的左孩子还是右孩子
		if (parentParent->_left == parent)
		{
			// 上层节点左边原本是parent,现在替换成新根subR
			parentParent->_left = subR;
		}
		else
		{
			// 上层节点右边原本是parent,现在替换成新根subR
			parentParent->_right = subR;
		}

		// 新根subR的父指针绑定上层节点parentParent,和大树连在一起
		subR->_parent = parentParent;
	}

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

4.2.3 先左单旋再右单旋

新节点插入在较高左子树的右侧,左右失衡:即parent的平衡因子是-2,cur的平衡因子是1,此时需要先以cur为旋转点进行左单旋,再以parent为旋转点进行一个右单旋

图1:

图2:

图3:

cpp 复制代码
void RotateLR(Node* parent)
{
	// 提取两个关键子节点
	Node* subL = parent->_left;  // parent左孩子
	Node* subLR = subL->_right;  // subL右孩子

	// 纪录旋转前subLR的平衡因子
	int bf = subLR->_bf;

	// 2. 两次单旋:先左旋subL,再右旋parent
	RotateL(subL);
	RotateR(parent);

	// 3.根据原subLR的bf,修正三个节点平衡因子
	if (bf == 0)  //图1
	{
		// 场景:插入节点就是subLR本身
		subL->_bf = 0;
		parent->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)  //图2
	{
		// 场景:新节点插在subLR的右子树
		subL->_bf = -1;
		parent->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == -1)  //图3
	{
		// 场景:新节点插在subLR的左子树
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

4.2.4 先右单旋再左单旋

新节点插入在较高右子树的左侧,右左失衡:即parent的平衡因子是2,cur的平衡因子是-1,此时需要先以cur为旋转点进行右单旋,再以parent为旋转点进行一个左单旋

图1:

图2:

图3:

cpp 复制代码
void RotateRL(Node* parent)
{
	// 1. subR:失衡节点parent的右孩子
	Node* subR = parent->_right;
	// 2. subRL:subR的左孩子,新增节点所在子树,双旋后成为新根
	Node* subRL = subR->_left;
	// 3. 提前保存subRL原始平衡因子,旋转会修改节点bf,必须先备份用于后续修正
	int bf = subRL->_bf;
	
	// 先进行右旋再进行左旋
	RotateR(parent->_right);
	RotateL(parent);
	
	// 场景1:bf=0,新增节点就是subRL自身,三者旋转后左右高度全部相等
	if (bf == 0)  //图1
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	// 场景2:bf=1,新增节点在subRL右子树,parent左子树更浅,平衡因子-1
	else if (bf == 1)  //图2
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	// 场景3:bf=-1,新增节点在subRL左子树,subR右子树更深,平衡因子1
	else if (bf == -1)  //图3
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

4.3 AVL树插入完整代码

cpp 复制代码
public:
bool Insert(const pair<K, V>& kv)
{
	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->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	cur->_bf = 0;

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

	cur->_parent = parent;

	while (parent)
	{
		if (cur == parent->_right)
		{
			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 && cur->_bf == -1)
			{
				RotateR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}

			break;
		}
		else
		{
			// 插入之前这棵树就有2/-2 bf的节点,这棵树之前就不是AVL树
			assert(false);
		}
	}

	return true;
}

private:

	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 (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}

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

	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 (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}

			subR->_parent = parentParent;
		}

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

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		RotateL(subL);
		RotateR(parent);

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

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_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);
		}
	}
	}

4.4 AVL树的其他功能函数

InOrder:二叉树中序遍历

Find:根据key查找对应节点

IsBalanceTree:判断树是否平衡,检查平衡因子

Height:获取该树高度

Size:求二叉树节点的个数

注意:每个节点子树高度的绝对值不超过1

cpp 复制代码
public:
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

private:
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

	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 _IsBalanceTree(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int bf = rightHeight - leftHeight;
		if (abs(bf) >= 2 || bf != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return _IsBalanceTree(root->_left)
			&& _IsBalanceTree(root->_right);
	}
	
		void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
	
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		//cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

5、测试AVL树

cpp 复制代码
int main()
{
	AVLTree<int, int> t;
	// 常规的测试用例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		if (e == 14)
		{
			int x = 0;
		}

		t.Insert({ e, e });
		//t.InOrder();
		cout << "Insert:" << e << "->" << t.IsBalanceTree() << endl;
	}
	t.InOrder();
	cout << t.IsBalanceTree() << endl;
}

输出结果:

Insert:4->1

Insert:2->1

Insert:6->1

Insert:1->1

Insert:3->1

Insert:5->1

Insert:15->1

Insert:7->1

Insert:16->1

Insert:14->1

1 2 3 4 5 6 7 14 15 16

1

相关推荐
杜子不疼.1 小时前
【C++】继承—C++的秘密武器,get父类的智慧
开发语言·c++
ShineWinsu2 小时前
对于Linux:基于UDP实现简单聊天室功能
linux·c++·面试·udp·笔试·进程·简单聊天室
chase_my_dream2 小时前
2D-SLAM 真实数据处理与多传感器工程落地:时间同步、异常过滤、标定对齐和系统调试
c++·人工智能·2d-slam
c238562 小时前
《序列 DP:C++ 中的“最长”套路与编辑距离》
c++·算法·动态规划
鱼子星_3 小时前
【C++】类和对象(下)——初始化列表,类型转换,static成员,友元,内部类,匿名对象,编译器的优化拓展
c语言·c++·笔记
王老师青少年编程10 小时前
2025年【江苏“信息与未来”编程思维】真题及题解(T2:坐标变换)
c++·题解·真题·坐标变换·编程思维·江苏·信息与未来
2023自学中14 小时前
C++ 内存追踪器
linux·c++
DogDaoDao19 小时前
【GitHub】 LLVM Project 深度解析:现代编译器基础设施的基石
java·c++·python·程序员·github·编译器·llvm
KuaCpp19 小时前
C++进程(下)
c++