数据结构学习笔记 --- 二叉平衡树AVL

文章介绍了AVL树的平衡机制,重点讲解了如何通过计算平衡因子、检测最小不平衡子树以及进行左旋和右旋操作来确保在插入和删除节点后保持树的平衡。
摘要由CSDN通过智能技术生成

平衡二叉树AVL

在多次插入和删除操作后,二叉搜索树可能退化为链表。在这种情况下,所有操作的时间复杂度将从 O(log⁡n) 劣化为 O(n) 。如图所示,经过两次删除节点操作,这棵二叉搜索树便会退化为链表。

在这里插入图片描述

再例如,在图所示的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之劣化。

在这里插入图片描述

判断条件
  • 是「二叉排序树」
  • 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
相关概念
平衡因子 BF

定义:左子树和右子树高度差

计算:左子树高度 - 右子树高度的值

别名:简称 BF(Balance Factor 而不是 Boy Friend)

一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要「旋转」纠正

获取平衡因子

/* 获取平衡因子 */
int balanceFactor(TreeNode *node) {
    // 空节点平衡因子为 0
    if (node == nullptr)
        return 0;
    // 节点平衡因子 = 左子树高度 - 右子树高度
    return height(node->left) - height(node->right);
}
最小不平衡子树

距离插入节点最近的,并且 BF 的绝对值大于 1 的节点为根节点的子树。

「旋转」纠正只需要纠正**「最小不平衡子树」**即可

节点类

由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 height 变量:

/* AVL 树节点类 */
struct TreeNode {
    int val{};          // 节点值
    int height = 0;     // 节点高度
    TreeNode *left{};   // 左子节点
    TreeNode *right{};  // 右子节点
    TreeNode() = default;
    explicit TreeNode(int x) : val(x){}
};
获取和更新高度
//获取高度
int height(TreeNode* node)
{
    return node == nullptr ? -1 : node->height;
}

//更新节点高度
void updateHeight(TreeNode* node)
{
	// 节点高度等于最高子树高度 + 1
	node->height = max( height(node->left), height(node->right)) + 1;
}
旋转
2 种「旋转」方式:
  1. 左旋
    • 旧根节点为新根节点的左子树
    • 新根节点的左子树(如果存在)为旧根节点的右子树
  2. 右旋:
    • 旧根节点为新根节点的右子树
    • 新根节点的右子树(如果存在)为旧根节点的左子树
4 种「旋转」纠正类型:
  1. LL 型:插入左孩子的左子树,右旋
  2. RR 型:插入右孩子的右子树,左旋
  3. LR 型:插入左孩子的右子树,先左旋,再右旋
  4. RL 型:插入右孩子的左子树,先右旋,再左旋

如图

在这里插入图片描述

四种旋转情况的选择条件

失衡节点的平衡因子子节点的平衡因子旋转方法
> 1(左偏树)>=0右旋
> 1(左偏树)<0先左旋后右旋
< -1(右偏树)<=0左旋
< -1(右偏树)> 0先右旋后左旋

代码:

//右旋操作
TreeNode* rightRotate(TreeNode* node) {
	TreeNode* child = node->left;
	if (child->right != nullptr)
	{
		TreeNode* grandChild = child->right;
		child->right = node;
		node->left = grandChild;
	}
	else
	{
		child->right = node;
	}
	updateHeight(node);
	updateHeight(child);
	return child;
}

//左旋操作
TreeNode* leftRotate(TreeNode* node) {
	TreeNode* child = node->right;
	if (child->left != nullptr) {
		TreeNode* grandChild = child->left;
		child->left = node;
		node->right = grandChild;
	}
	else
	{
		child->left = node;
	}
	updateHeight(node);
	updateHeight(child);
	return child;
}
旋转的选择
//旋转的选择
TreeNode* rotate(TreeNode* node) {
	int balfactor = balanceFactor(node);
	if (balfactor > 1)
	{
		//左偏树
		if (balanceFactor(node->left) >= 0) return rightRotate(node);
		else {
			node->left = leftRotate(node->left);
			return rightRotate(node);
		}
	}
	if (balfactor < -1)
	{
		//右偏树
		if (balanceFactor(node->right) <= 0) return leftRotate(node);
		else {
			node->right = rightRotate(node->right);
			return leftRotate(node);
		}
	}
	return node;
}
插入节点

AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡。代码如下所示:

TreeNode* AvlTree::insertHelper(TreeNode* node, int val) {
	if (node == nullptr)
		return new TreeNode(val);
	/* 1. 查找插入位置并插入节点 */
	if (val < node->val)
		node->left = insertHelper(node->left, val);
	else if (val > node->val)
		node->right = insertHelper(node->right, val);
	else
		return node;    // 重复节点不插入,直接返回
	updateHeight(node); // 更新节点高度
	/* 2. 执行旋转操作,使该子树重新恢复平衡 */
	node = rotate(node);
	// 返回子树的根节点
	return node;
}
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值