【C++】AVL树

📕 概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序,二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和 E.M.Landis 在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

📕 具体实现

节点定义

如下,AVL 树相比二叉搜索树,还要多一个指向父亲节点的指针,以及影响因子。

影响因子代表的是该节点左右子树的高度差。假设当前有一个节点 A,A左右子树均为空。如果在A 的左边插入一个节点,那么 A 的影响因子就 -1 ,代表 A 的左子树比右子树高度高1。如果此时A的右边再插入一个节点,那么A 的影响因子就 +1,变成了0,代表A 的左右子树高度相同。

并且,A节点的影响因子变动,也会影响 A 的父亲节点的影响因子。

template<class K, class V>
	struct AVLTreeNode
	{
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		pair<K, V> _kv;
		int _bf; // balance factor  影响因子

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

框架

如下,是AVL树的框架。

#pragma once
#include<iostream>
#include<cassert>

using namespace std;

namespace simulate
{
	template<class K, class V>
	struct AVLTreeNode
	{
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		pair<K, V> _kv;
		int _bf; // balance factor  影响因子

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


	template<class K, class V>
	class AVLTree
	{
		typedef AVLTreeNode<K, V> Node;

	public:
		// 成员方法 …………
	private:
		Node* _root=nullptr;
	};
}

插入

AVL 树的插入,本质上和搜索二叉树的插入类似。找到插入位置之后,插入节点。
但是 AVL 树插入节点之后,还需要检查是否平衡,如果不平衡(左右子树高度差超过1),那么就不满足 AVL 树的性质,需要进行旋转来降低高度,达到平衡的状态。

所以如下,插入节点的代码,前半部分和 搜索二叉树别无二致,后面检查平衡因子才是重头戏。

如下,插入 cur 节点之后,其父亲节点的平衡因子也受到以下,由于 cur 修改在 parent 的右边,所以 parent 的平衡因子 +1 。但是,这就结束了吗?
请添加图片描述
通过上图,很明显看出这已经不是 AVL 树了,8节点的左右子树高度差为2,需要旋转。

但是,如果更新平衡因子的方法,只按照上面说的,更新一轮 cur 与 parent,极有可能是检查不到错误的,所以必须往上继续更新。如下,将 cur 指向 其父亲节点,parent 也指向其父亲节点,继续检查平衡因子。
当检查到某个节点的平衡因子为 2 或者 -2 的时候,就说明其左右子树高度差为2了,需要旋转来降低高度。
请添加图片描述

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->_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;
			}
			else if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			cur->_parent = parent;



			// 调整平衡因子
			while (parent)
			{
				if (cur == parent->_right)
					parent->_bf++;
				else if (cur == parent->_left)
					parent->_bf--;

				if (parent->_bf == 1 || parent->_bf == -1)
				{
					parent = parent->_parent;
					cur = cur->_parent;
				}
				else if (parent->_bf == 0)
					break;
				else if (parent->_bf == 2 || parent->_bf == -2)
				{
					// 旋转
					if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotateL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == -1)
					{
						RotateR(parent);
					}
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						RotateRL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						RotateLR(parent);
					}
					else
						assert(false);

					break;
				}
				else
				{
					assert(false);
				}
			}


			return true;
		}

那么,如何进行平衡因子的更新,也就成了插入操作里的重点。

对于插入的 cur 节点,它一定是在 parent 节点的左子树或者右子树,那么 parent 的平衡因子就可以由此进行 -1 或者 +1 操作。更新之后的 parent 节点的平衡因子可能有三种情况:0、-1 或者 1、2 或者 -2。

  • 更新之后的 parent 节点平衡因子是 1 或者 -1。如下情况1,就是这一类,说明 parent 节点原本的平衡因子是 0,所以更新之后才会是 1 或者 -1。那么这时就说明,parent 为根节点的子树,其高度增加了1,这肯定会影响 parent 节点的父亲节点(此图中是值为 8 的节点)。所以必须要向上更新平衡因子,只需要 把 cur 指向其父亲节点,parent 指向其父亲节点,然后进行同样的判定即可。
  • 更新之后的 parent 节点,平衡因子是 0 。如下情况2 ,说明其原本是不平衡的,更新之后为0,说明变平衡了,但是 以 parent 节点为根节点的子树,其高度没有增加,那么就没有向上更新平衡因子的必要。
  • 更新之后的 parent 节点,平衡因子是 2 或者 -2。说明 parent 左右子树高度差为2,需要旋转。

请添加图片描述

★ 旋转 ★

要进行旋转,必然是更新之后某个节点的平衡因子为 2 或者 -2,那么更新之前,该节点的平衡因子必然是 1 或者 -1,也就是说,更新前,该节点的 左子树 比 右子树高 1,或者右子树比左子树高1,可以分为这两种情况。

情况一

如下,左子树比右子树高1,如果插入的节点是在 a 区域,那么 30 这个节点的平衡因子就变成了 -1,60 的平衡因子是 -2。这种情况可以进行右单旋转,如图,将 30 放到最高,60 放低。但是注意,60 上面可能还有父亲节点,只是这里没画出来,旋转也要处理 60 的父亲节点。
所以,下图中,这种情况下涉及到的节点是 father(值为60),subL(值为30),subLR(b部分的根节点),pparent(最左边的状态时,值为60的父亲节点)。

第一种情况,旋转前,60 和 30 两个节点构成 / 的形状(便于记忆,右边高,进行右单旋)。
请添加图片描述

情况二

如下,右子树比左子树高1,如果插入的节点是在 c 这个部分,那么 60 节点的平衡因子就变成了 1,30 节点的平衡因子就变成了 2 ,此时可以进行左单旋转,如图所示。
下图中,涉及到的节点是和上面基本一样的,father(值为 30),subR(值为 60),subRL(b 部分的根节点),pparent(最左边的状态时,30的父亲节点)。

第二种情况,旋转前,30 和 60 两个节点构成 \ 的形状(便于记忆,左边高,进行左单旋)。
请添加图片描述

情况三

但是,左子树高时,如果插入在左子树的另一边呢?如下,插入前,依然是左子树比右子树高1,此时不插入 a 部分,而是插入左子树的另一部分(即 b 部分或者 c 部分),此时值为60的节点,平衡因子是 -1,值为 30 的节点,平衡因子是 1,值为 90 的节点,平衡因子是 -2。这又是另一种情况了,因为这种情况要进行两次旋转。

第一次,先把值为 30 的节点看作根节点,即 90 节点和 d 部分忽略。很明显,以 30 为根节点的部分,是上面需要左单旋的情况。所以直接进行左单旋。

第二次,以 90 为根节点,进行右单旋即可。

插入在 b 或者 c 部分,影响的只是旋转之后,平衡因子的更新,其他没有什么影响。此外,如果插入在 c 部分,那么插入之后,值为 60 的节点平衡因子就是 1,值为 30、90 节点的平衡因子和插入在 b 部分之后是一样的。而我们就可以根据这一点,来判断插入在 b 区域还是 c 区域(设置一个变量记录 60 这个节点的平衡因子),进而根据情况来修改旋转后的平衡因子。
如下图,旋转之后,平衡因子受到影响的只有 30、60、90 这三个节点,插入在 b 还是 c,影响的是 30、90 这两个节点。

第三种情况, 旋转前,90、30、60 节点构成 < 的形状,要进行两次旋转,先从下面开始,下面是 \ ,所以先左单旋转,上面是 / ,所以再右单旋。

请添加图片描述

情况四

如下,是最后一种情况。
插入前,右子树高,但是不插入在 d 部分,插入在 b 或者 c 部分。

第一步,以 90 节点为根节点,进行右单旋。

第二步,以 30 节点为根节点,进行左单旋。

其余思想均和第三种情况一样,就不过多赘述。

第四种情况,旋转前,30、90、60 这三个节点构成 > 的形状,从下往上,先是 / ,所以右单旋,再是 \ ,所以再左单旋。
请添加图片描述

代码如下。当然了,这几个旋转方法都是成员函数内部使用的,所以用 private 修饰,对外不可见。

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

			parent->_right = subRL;
			if(subRL)
				subRL->_parent = parent;

			Node* pparent = parent->_parent;
			subR->_left = parent;
			parent->_parent = subR;

			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;

		}



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

			Node* pparent = parent->_parent;
			subL->_right = parent;
			parent->_parent = subL;

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


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

				subL->_parent = pparent;
			}

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

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

			RotateL(parent->_left);
			RotateR(parent);

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

判断平衡

如下,判断平衡的关键在于,左右子树的高度差是否和平衡因子的绝对值相同,所以,需要知道左右子树的高度,要用到 Height() 来辅助判断。

public:
		bool IsBalance()
		{
			return _isBalance(_root);
		}

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

private:
		bool _isBalance(Node* root)
		{
			if (root == nullptr)
				return true;
			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			if (root->_bf != rightH - leftH)
			{
				cout << root->_kv.first << "号平衡因子错误" << endl;
				root->_bf = rightH - leftH;
			}

			return abs(rightH - leftH) < 2
				&& _isBalance(root->_left)
				&& _isBalance(root->_right);
		}

		int _Height(Node* root)
		{
			if (root == nullptr) 
				return 0;

			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			return leftH > rightH ? leftH + 1 : rightH + 1;
		}

📕 源代码

#pragma once
#include<iostream>
#include<cassert>

using namespace std;

namespace simulate
{
	template<class K, class V>
	struct AVLTreeNode
	{
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		pair<K, V> _kv;
		int _bf; // balance factor  影响因子

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


	template<class K, class V>
	class AVLTree
	{
		typedef AVLTreeNode<K, V> Node;

	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->_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;
			}
			else if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			cur->_parent = parent;



			// 调整平衡因子
			while (parent)
			{
				if (cur == parent->_right)
					parent->_bf++;
				else if (cur == parent->_left)
					parent->_bf--;

				if (parent->_bf == 1 || parent->_bf == -1)
				{
					parent = parent->_parent;
					cur = cur->_parent;
				}
				else if (parent->_bf == 0)
					break;
				else if (parent->_bf == 2 || parent->_bf == -2)
				{
					// 旋转
					if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotateL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == -1)
					{
						RotateR(parent);
					}
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						RotateRL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						RotateLR(parent);
					}
					else
						assert(false);

					break;
				}
				else
				{
					assert(false);
				}
			}


			return true;
		}

		void Inorder()
		{
			_Inorder(_root);
		}

		bool IsBalance()
		{
			return _isBalance(_root);
		}

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

	private:

		bool _isBalance(Node* root)
		{
			if (root == nullptr)
				return true;
			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			if (root->_bf != rightH - leftH)
			{
				cout << root->_kv.first << "号平衡因子错误" << endl;
				root->_bf = rightH - leftH;
			}

			return abs(rightH - leftH) < 2
				&& _isBalance(root->_left)
				&& _isBalance(root->_right);
		}

		int _Height(Node* root)
		{
			if (root == nullptr) 
				return 0;

			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			return leftH > rightH ? leftH + 1 : rightH + 1;
		}

		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_Inorder(root->_left);
			cout << root->_kv.first << " ";
			_Inorder(root->_right);
		}

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

			parent->_right = subRL;
			if(subRL)
				subRL->_parent = parent;

			Node* pparent = parent->_parent;
			subR->_left = parent;
			parent->_parent = subR;

			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;

		}



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

			Node* pparent = parent->_parent;
			subL->_right = parent;
			parent->_parent = subL;

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


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

				subL->_parent = pparent;
			}

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

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

			RotateL(parent->_left);
			RotateR(parent);

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

		}


	private:
		Node* _root=nullptr;
	};
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力.xx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值