平衡树的模拟实现

一.平衡树的介绍

平衡树是以二叉树结构为基础,同时引入了平衡因子进行了限制,以保证树的结点之间的高度差小于等于1,在插入删除结点时通过旋转的方法保持高度相对平衡,从而提高搜索等效率。

二.代码实现

1.平衡树结点

平衡树结点是以二叉树为基础构建的,因此需要左右结点指针left与right。传入pair一部分为值value另一部分为关键字key,以及平衡因子bf(左子树高度减右子树高度)。

下面是结点代码:

cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;

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

2.平衡树的插入

插入结点时首先要找到合适的位置空间来放置新结点,所以我们需要遍历树。从根结点开始(cur),若值比当前结点小让cur往左边走,若值比当前结点大让cur往右边走,直到cur跑到空时停止。

接着进行判断,根据结点高度差的不同,结点位置不一样进行不同的旋转方式,调整相应的平衡因子,根据parent结点的高度决定是否要继续向上更新。

下面是寻找结点的相应代码:

cpp 复制代码
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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

若树的结点为空那么直接插入根结点,若不为空按照上述的方法遍历到空结点后,需要注意要确定parent的位置。

下面我们来讨论一下旋转

首先计算平衡因子,若cur在parent的左边则bf--,若cur在parent的右边则bf++,若parent的bf为0时直接跳出结果。

若parent的左右都存在结点,那么就是平衡的,反之则不平衡,若bf等于1或者-1,时继续向上更新bf平衡值,让cur等于parent,parent等于parent的parent。

向上遍历若得到bf等于2或者-2时,说明此时树已经不平衡了,需要进行旋转调整树的结构。

下面我们依次分析左旋,右旋,左右双旋,右左双旋的情况

1.右单旋

当parent的平衡因子为-2,cur的平衡因子为-1时我们需要右单旋

如图p代表parent,c代表cur此时我们需要进行右旋操作d代表插入的结点。以parent为轴向右旋转让cur变成parent。

我们需要得到parent结点,cur结点(SUL),以及cur结点的右结点(SULR)。

让SULR变成parent的左结点SUL的右结点变成parent,其他的不进行改变。

需要注意的是,首先需要判断SULR结点是否存在,若存在就让其变为parent的左结点,若不存在就不进行操作。以及我们需要找到parent结点的parent结点,若parentparent结点为空则则让root结点直接变为SUL即可,若不为空需要判断parent结点是parentparent结点的左还是右结点,然后将其左或右给给SUL。

下面放上代码:

cpp 复制代码
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;

		//parent有可能是整棵树的根,也有可能是局部子树
		//如果是整棵树的根则修改root
		//
		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;
	}

2.左单旋

左单旋与右单旋类似,只是parent结点的左右子树互换了位置。

当parent的bf为2时,cur的bf为1时,需要进行左旋操作。

如图p代表parent ,c代表cur结点,插入的结点为a。以parent为轴向左旋转。

我们需要得到cur结点的左结点(SURL),parent结点以及cur结点(SUR)。让parent的右为SURL,SUR的左为parent。其他的不进行改变。

与上文相同需要找到parent的parent,若parentparent不存在就将root结点设置为SUR,若存在,则根据parent是parentparent的左或者右结点来分配给SUR结点。

下面是代码:

cpp 复制代码
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 (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
		parent->_bf = subR->_bf = 0;
	}

3.左右双旋

当parent的bf等于-2,cur的bf等于1时,进行左右双旋。先对cur进行左旋让其bf等于-1,此时就回到了第一种情况,再对parent进行一次右旋就完成了操作。

当新增结点cur结点与parent结点形成一个角度时,需要进行双旋操作,如果是开口向右则进行左右双旋,若开口向左则进行右左双旋。

此处的难点在于对插入结点bf的分类讨论。

第一种情况 当SULR(R)的bf为0时

此时我们需要先对SUL进行左旋操作,使其满足三个点连成一条直线,之后再对parent进行右旋操作,得到下图。

我们可以根据最终结果直接写出代码,先对SUL进行左旋再对parent进行右旋,最后设置三个结点的bf都为0即可。

第二种情况 当SULR(R) 的bf为-1时

当我们进行左右双旋操作后

变成如图,此时需要将parent的bf设置为1,SUL和SULR设置为0。

第三种情况 当SURL(R)的bf为1时

进行左右双旋操作之后

将SUL的bf设置为-1,parent与SULR的bf设置为0即可。

下面附上代码:

cpp 复制代码
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

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

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

4.右左双旋

右左双旋本质与左右双旋一样,只是左右子树位置互换

所以我就直接附上代码:

cpp 复制代码
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)
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

5.插入代码总结

cpp 复制代码
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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = 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 = parent->_parent;
			}
			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)//右左双旋
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
				{
					RotateLR(parent);
				}
				else if(parent->_bf == -2 && cur->_bf == -1)//右单旋
				{
					RotateR(parent);
				}
				break;
			}
			else
			{
				//说明传入的有问题报错日志
				assert(false);
			}
		}
		return true;
	}

	//右单旋
	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;

		//parent有可能是整棵树的根,也有可能是局部子树
		//如果是整棵树的根则修改root
		//
		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;
	}

	//左单旋
	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 (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
		parent->_bf = subR->_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 == 0)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 1;
		}
		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)
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

3.键值查找 树的高度计算 与平衡树判断

查找值时,只需要遍历整个树即可,以根节点(cur)为起始点,当所找的值比cur小时向左树寻找,比cur大时向右树寻找,直到cur值等于寻找值时返回当前结点,若cur为空了仍未找到结点就返回空。

cpp 复制代码
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;
	}

计算树的高度时我们采用递归左右子树的方法实现,判断左右子树的大小取大值,逐层递归得到最终值。

cpp 复制代码
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;
	}

判断是否平衡时,我们可以调用左右子树的高度,然后相减,得到diff。若diff大于2说明高度异常,若根结点的bf不等于diff说明平衡因子计算存在错误。

cpp 复制代码
bool _IsBalanceTree(Node* root)
	{
		if (nullptr == root)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = leftHeight - rightHeight;

		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "高度异常" << endl;
		}
		if (root->_bf != diff)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}

三.总结

平衡树要求任意节点的左右子树高度差绝对值不超过 1,且左右子树本身也是平衡树。通过限制树的高度(保持在 O(logn) 级别),确保查找、插入、删除等操作的时间复杂度稳定在 O(logn),避免退化为链表的 O(n) 复杂度。每次插入或删除后通过旋转(左旋、右旋、左右双旋、右左双旋)立即恢复平衡。

它的优点在于时间复杂度低,适合大量数据的操作,缺点在于需要动态的更新数据进行旋转增大了时间开销。

相关推荐
就很对6 分钟前
7种数据结构
数据结构·windows
Aomnitrix12 分钟前
Qt 实操记录:打造自己的“ QQ 音乐播放器”
开发语言·c++·qt·ui·音视频
wzysyrda14 分钟前
CRTP奇异递归模板模式
c++
f狐0狸x27 分钟前
【蓝桥杯每日一题】3.17
c语言·c++·算法·蓝桥杯·二进制枚举
爱康代码1 小时前
【c语言数组精选代码题】
c语言·开发语言·数据结构
Elnaij2 小时前
从C语言开始的C++编程生活(1)
c语言·c++
此刻我在家里喂猪呢2 小时前
C++ 介绍STL底层一些数据结构
c++
PingdiGuo_guo2 小时前
C++前缀和
开发语言·c++
zmhzmhzm3 小时前
如何理解std::promise和std::future
c++
共享家95273 小时前
顺序表的C语言实现与解析
数据结构·算法