红黑树和 AVL 树的旋转调整以及性能分析

本文详细介绍了AVL树的节点定义、旋转分类及其特点与性能分析,包括右单旋、左单旋和双旋等。接着探讨了红黑树的引入原因、特性、插入操作及旋转调整,强调了红黑树在实际应用中的优势。内容涵盖两种平衡二叉搜索树的验证方法和应用场景。

一、AVL 平衡搜索树

二叉搜索树虽可以缩短查找的效率,但如果数据极端,退化为链表,搜索时间复杂度为O(N),所以引入了ALV树,在二叉搜索树的基础加入平衡因子

平衡因子:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1

1、节点定义

template<class T>
struct AVLTreeNode{
	AVLTreeNode(const T& data)
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _bf(0)//平衡因子,初始值为0,默认已经平衡
	{}
	AVLTreeNode<T>* _pLeft; // 该节点的左孩子
	AVLTreeNode<T>* _pRight; // 该节点的右孩子
	AVLTreeNode<T>* _pParent; // 该节点的双亲
	T _data;
	int _bf; // 该节点的平衡因子
};

2、AVL 树旋转分类

2.1. 右单旋

在这里插入图片描述
45节点 的存在与否都不重要,如果40 没有右子树,那么它依然要进行单旋。上面的案列 50 没有双亲节点,如果有父节点,那该怎么调整呢?

就拿上列来说,我们假设 50 还有双亲节点

	void RotateRight1(Node* pParent){
		// pParent 代表的是 50 节点
		// pSubL: pParent的左孩子 40
		// pSubLR: pParent左孩子的右孩子 45 
		Node* pSubL = pParent->_pLeft;
		Node* pSubLR = pSubL->_pRight;
		// 旋转完成之后,40 的右孩子作为双亲的左孩子
		pParent->_pLeft = pSubLR;
		
		// 如果 50 的左子树的右孩子存在,更新亲双亲
		// 使 45 的双亲指向 50
		if (pSubLR) {
			pSubLR->_pParent = pParent;
		}
		
		// 50 作为 40 的右孩子
		pSubL->_pRight = pParent;
		// 因为 50 可能是棵子树,因此在更新其双亲前必须先保存 50 的双亲
		Node* pPParent = pParent->_pParent;
		// 更新 50 的双亲
		pParent->_pParent = pSubL;
		// 更新 40 的双亲
		pSubL->_pParent = pPParent;
		// 如果 50 是根节点,根新指向根节点的指针
		if (nullptr == pPParent){
			pRoot = pSubL;
			pSubL->_pParent = nullptr;
		}
		else{
			// 如果 50 是子树,可能是其双亲的左子树,也可能是右子树
			if (pPParent->_pLeft == pParent) {
				pPParent->_pLeft = pSubL;
			}
			else {
				pPParent->_pRight = pSubL;
			}
		}
		// 根据调整后的结构更新部分节点的平衡因子
		pParent->_bf = pSubL->_bf = 0;
	}
2.2 左单旋

和右单旋正好相反

2.3. 双旋 — 先左单旋 再 右单旋

在这里插入图片描述

2.4. 双旋 — 先右单旋 再 左单旋

和上面的正好相反

2.5. 总结

在这里插入图片描述
旋转完成后,原 pParent 为根的子树个高度降低,已经平衡,不需要再向上更新

3、AVL树的特点与性能分析

  • 需要通过多次的旋转来保证搜索性能

  • 左右子树都是AVL树

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

  • 搜索时间复杂度O(log2N )

4、AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树,如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

  2. 验证其为平衡树,判断每个节点的左子树高度和右子树高度,如果全部都在 -1 ~ 1 的范围,则说明平衡

	//计算深度
	int Height(Node* pRoot){
		if (root == NULL){
			return 0;
		}
		if (root->left == NULL && root->right == NULL){
			return 1;
		}
		int left = GetHeight(root->left);
		int right = GetHeight(root->right);
		return max(left,right)+1;
	}

	//判断是否平衡
	bool IsBalanceTree(Node* pRoot){
		if (nullptr == pRoot) {
			return true;
		}
		// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = Height(pRoot->_pLeft);
		int rightHeight = Height(pRoot->_pRight);
		int diff = abs(rightHeight - leftHeight);
		if (diff > 1)){
			return false;
		}
		return IsBalanceTree(pRoot->_pLeft) && IsBalanceTree(pRoot->_pRight);
	}

二、红黑树

1、为什么引入红黑树?

红黑树和AVL树都是高效的平衡二叉树,搜索时间复杂度都是O(log2N ),只不过AVL树旋转的次数太多,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优 而且红黑树实现比较简单,所以实际运用中红黑树更多

2、红黑树特性

在这里插入图片描述

  • 每个结点不是红色就是黑色
  • 根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点是黑色的(不能有连在一起的红色节点)
  • 对于每个结点,从该结点到所有后代叶结点的简单路径上,黑色节点相同
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

只要满足上述性质,可以保证红黑树最长路径不会超过最短路径的两倍

3、RBTree 是如何插入?

插入和AVL树一样,而且不需要进行平衡因子的判断,相对来说比较简单

4、RBTree 如何旋转的?

4.1情况1

cur 是红色,p 也是红色,且 u 节点存在也为红

在这里插入图片描述

正确操作:把 p 和 u 改为黑色(如果 g 为根节点)
在这里插入图片描述

如果 g 不是根节点,上述把双亲节点和叔叔节点变为黑树就违反了红黑树的性质,因为右子树分别多了一个黑色节点

正确做法:把 g 变为红色节点

在这里插入图片描述

当然对于 g 双亲节点的颜色,如果是黑色的,则不用调整了,但是如果是红色节点,则还需要进行调整

分析:对于 cur 节点有两种场景

  • 新插入的节点
  • 本来是黑色,但是子树不满足性质,把 cur 变为了红色
4.2情况2

cur为红,p为红,g为黑,u不存在或者 u 存在一定为黑

根据 u 对 cur 节点进行分析

  • 如果 u 不存在,则 cur 一定是插入的节点
  • 如果 u 存在且为黑色,则 cur 本来的颜色就是黑色(这种情况就是 4.1情况中的 g 双亲节点为红色)

在这里插入图片描述

这种情况时候无论怎么变换颜色,都会破坏红黑树性质,所以

如果 p 为 g 的左孩子,cur 为 p 的左孩子,则进行右单旋
如果 p 为 g 的右孩子,cur 为 p 的右孩子,则进行左单旋
在这里插入图片描述

最后 p ,g 变色

在这里插入图片描述

4.3情况3

cur为红,p为红,g为黑,u不存在/u为黑

在这里插入图片描述

和情况2特别相似,我们只要对 p 做左单旋就会发现

在这里插入图片描述
又变回了情况2,所以按照情况2的处理方式进行处理就可以了

5、红黑树验证

1、验证根节点颜色
2、验证任意路径黑色节点的个数
3、验证红色节点的左右节点是否是红色

6、红黑树应用

C++ STL 库,作为 map ,set 的底层结构
Java 库
Linux 库

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿的温柔香

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

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

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

打赏作者

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

抵扣说明:

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

余额充值