手撕AVL树——四种旋转

10 篇文章 0 订阅
本文介绍了二叉搜索树和AVL树的概念,重点讲解了AVL树的平衡策略和四种旋转操作:左单旋、右单旋、左右双旋、右左双旋,并通过平衡因子解释了何时进行哪种旋转。同时,提供了不同旋转情况的分析和示例图,帮助理解AVL树的插入和平衡过程。
摘要由CSDN通过智能技术生成

二叉搜索树

二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

在这里插入图片描述
对二叉搜索树进行中序遍历可以得到有序的序列

AVL树

是一颗严格平衡搜索树通过控制左右子树的高度差,左右子树的高度之差的绝对值超过(-1/0/1)

我这里用的是三叉链,用平衡因子控制左右之差
三叉链可以更方便操作和理解,也是有弊的,每个结点在32为下指针为四个字节,但是利大于弊还是很不错的,方便

template<class K, class V>
struct AVLTreeNode //结点的定义
{
	AVLTreeNode<K, V>* _left; //左子树
	AVLTreeNode<K, V>* _right; //右子树
	AVLTreeNode<K, V>* _parent; //父亲

	int _bf;//平衡因子 balance factor

	pair<K, V> _kv; 

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

AVL树的精华在于它的插入,就主要讲这个方面

平衡时通过旋转来完成的,有四种旋转
1.左单旋
2.右单旋
3.左右双旋
4.右左双旋
这几点我们可以通过结点的平衡因子来判断时那种情况
a.在根的左边插入时–,右边时++,就是右节点-左节点
在这里插入图片描述
像这种的是10的结点右边插入了20,在右边插入所以10结点的平衡因子++而新增的点两边都是空,平衡因子是0

上插入的情况

//迭代插入,不用递归
pair<Node*, bool> Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return make_pair(_root, true);
		}

		//循坏插入
		Node* parent = _root, *cur = _root;
		while (cur)
		{
				//用pair的first比较
			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 make_pair(cur, false);
			}
		}

		cur = new Node(kv);
		Node* newnode = cur;

		if (parent->_kv.first > cur->_kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		
		while (parent)
		{	
			//在左边父亲的bf--
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else //在右边bf++
			{
				parent->_bf++;
			}
			//更新完了如果父亲的bf变成0,就表示两边平衡了
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//parent所在的子树高度变了,会影响parent->parent
				//继续往上更新
				cur = parent;
				parent = parent->_parent;

			}//下面这些可以先不看,看图
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				if (parent->_bf == -2)
				{
					if (cur->_bf == -1)
					{
						RotateR(parent);
					}
					else if(cur->_bf == 1)
					{
						RotateLR(parent);
					}
				}
				else if (parent->_bf == 2)
				{
					if (cur->_bf == 1)
					{
						RotateL(parent);
					}
					else if (cur->_bf == -1)
					{
						RotateRL(parent);
					}
				}
				
				break;

			}
			else
			{
				assert(false);
			}
		
		}
		return make_pair(newnode, true);

	}

	

1.左单旋

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

		//把subR的左边给parent的右边
		parent->_right = subRL;

		//如果subRL不为空就把它的parent链接到parent
		if (subRL)
			subRL->_parent = parent;

		//把subR的左边链接到parent
		subR->_left = parent;

		//保存parent的父结点
		Node* parentparent = parent->_parent;

		//改变parent的指向关系
		parent->_parent = subR;

		//判断parent是不是其他树的子结点
		if (_root == parent)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			//这里就表示了是子结点,链接
			subR->_parent = parentparent;

			//判断parent是parentparent的左或右节点再链接subR
			if (parentparent->_left == parent)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
		}
		 
		//更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

在这里插入图片描述

2.右单旋

//右单旋
	void RotateR(Node* parent)
	{
		//右单旋表明右结点高,往下调整,用parent->_left作为新的父结点
		//subL的右结点做为parent->_left,再把subL->right = parent;subL的右结点链接parent
		
		//parent->_left
		Node* subL = parent->_left;
		//parent->_left->_right -> subL->_right
		Node* subLR = subL->_right;

		parent->_left = subLR;
		//可能为空, 更换父节点的指向
		if (subLR)
		{
			subLR->_parent = parent;
		}

		//右节点链接parent
		subL->_right = parent;
		//保存祖父结点
		Node* parentparent = parent->_parent;

		//改变parent的父结点的指向
		parent->_parent = subL;
		//判断parent是不是子节点
		if (_root == parent)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			subL->_parent = parentparent;
			
			if (parentparent->_left == parent)
			{
				parentparent->_left = subL;
			}
			else
			{
				parentparent->_right = subL;
			}
		}
		//更新平衡因子
		parent->_bf = subL->_bf = 0;

	}

这个图画的不太好,本人写博客就是当作笔记来写的比较随意,如果看不懂可以评论留言,在能力范围内的都会解答的
在这里插入图片描述

双旋

看见这个标题了吧,双旋比单旋难理解一点,但还行吧,不是很难,主要是控制平衡因子

双旋分为
a.左右双旋
b.右左双旋
两种情况

左右双旋

它的形状像是折线,而上面的单旋都是直线的形状
在这里插入图片描述

像这种的话就是要左右双旋来看个抽象图
在这里插入图片描述
来自他人的一张很棒的动图:
在这里插入图片描述
膜拜大佬好吧
虽然跟上面的新增位置不一样,但是本质是一样的,平衡因子的更新不同

具象图:
情况1:subLR->_bf == 1时的情况
在这里插入图片描述
情况二: subLR->_bf == -1时
在这里插入图片描述
情况三:subLR->_bf == 0时
在这里插入图片描述

右左双旋

情况一:
在这里插入图片描述
情况二:
在这里插入图片描述
情况三:
在这里插入图片描述
这些基本上就是AVL树的四种旋转了,可以自己画画图,理解理解

图和代码的链接

AVL树的部分实现和画图旋转

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学习新算法

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

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

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

打赏作者

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

抵扣说明:

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

余额充值