【C++练级之路】【Lv.15】AVL树(双子旋转,领略绝对平衡之美)

news2024/9/28 1:25:50



快乐的流畅:个人主页


个人专栏:《C语言》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、AVL树的概念
  • 二、AVL树的模拟实现
    • 2.1 结点
    • 2.2 成员变量
    • 2.3 插入
    • 2.4 旋转
      • 2.4.1 左单旋
      • 2.4.2 右单旋
      • 2.4.3 左右旋
      • 2.4.4 右左旋
  • 三、AVL树的验证
  • 四、AVL树的性能
    • 4.1 优势
    • 4.2 缺陷
    • 4.3 适用场景

引言

之前讲解了二叉搜索树,最优情况下它具有非常好的搜索性能,但是在极端场景下,它可能退化为单支树,可以形象地称为歪脖子树~


这样的话,它搜索的时间复杂度会从O(log2N)退化为O(N2),完全丧失了其优良的搜索性能。那么AVL树就可以登场了,它就是为解决这类问题而生的!

一、AVL树的概念

两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了AVL树,AVL树满足两条性质:

  • 它的左右子树都是AVL树
  • 任意结点的左右子树高度差的绝对值不超过1

这样通过控制子树高度差,让AVL树几乎完美接近于平衡,便不会出现单支树的情况,保证了优良的搜索性能,因此AVL树又称为高度平衡二叉搜索树

二、AVL树的模拟实现

2.1 结点

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)
		, _kv(kv)
		, _bf(0)
	{}
};

细节:

  1. 使用三叉链,增加了指向parent的指针
  2. 使用KV模型,数据存储键值对pair
  3. 结点存储平衡因子,用来记录左右子树高度差

注:平衡因子计算高度差,是 右子树高度 - 左子树高度

2.2 成员变量

template<class K, class V>
class AVLTree
{
protected:
	typedef AVLTreeNode<K, V> Node;
public:
protected:
	Node* _root = nullptr;
};

2.3 插入

因为AVL树也是二叉搜索树,所以默认成员函数和遍历与之前写的没什么不同,这里重点讲解AVL树的插入。

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->_right)
		{
			++parent->_bf;
		}
		else
		{
			--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)
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}
			break;
		}
		else
		{
			assert(false);
		}
	}

	return true;
}

思路:

  1. 以二叉搜索树的方式正常插入
  2. 讨论平衡因子,以及调整结构

这里的重点在于如何讨论和调整平衡因子(bf)。

  1. 首先,插入cur结点,调整parent结点的bf,左减右加
  2. 讨论parent的bf
    • bf为0
    • bf为1或-1
    • bf为2或-2

bf为0时:

分析:此时没有增加高度,而是补上缺口,整棵树是平衡的,直接break即可


bf为1或-1时:

分析:此时增加了局部子树的高度,不确定有没有影响整体的高度差,所以要继续向上调整

parent = parent->_parent;
cur = cur->_parent;

bf为2或-2时:

此时bf已经超出平衡限制区间,需要进行结构调整,我们称之为旋转

2.4 旋转

旋转分为两大类:单旋和双旋。而单旋分为左单旋和右单旋,双旋分为左右旋和右左旋。

2.4.1 左单旋

场景:右部外侧插入

过程:

  1. parent接过subR的左子树subRL
  2. subR左边再链接parent

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

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

	subR->_left = parent;
	parent->_parent = subR;

	if (grandparent)
	{
		if (grandparent->_right == parent)
		{
			grandparent->_right = subR;
		}
		else
		{
			grandparent->_left = subR;
		}
	}
	else
	{
		_root = subR;
	}
	subR->_parent = grandparent;

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

细节:

  1. 大体是三步链接,注意双向链接
  2. 注意判空(subRL,grandparent)
  3. 如果判空,注意_root的传递
  4. 最后调整平衡因子_bf

2.4.2 右单旋

场景:左部外侧插入

过程:

  1. parent接过subL的右子树subLR
  2. subL右边再链接parent

void RotateR(Node* parent)//右单旋
{
	Node* grandparent = parent->_parent;
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

	subL->_right = parent;
	parent->_parent = subL;

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

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

细节:同左单旋

2.4.3 左右旋

场景:左部内侧插入

过程:

  1. 先对subL进行左单旋
  2. 再对parent进行右单旋

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

	RotateL(subL);
	RotateR(parent);

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

细节:

  1. 这里旋转直接复用前面单旋的代码
  2. 主要的重点,在于平衡因子bf的讨论
    • bf为1,在subLR的右侧插入
    • bf为-1,在subLR的左侧插入
    • bf为0,插入subLR(之前为空)

2.4.4 右左旋

场景:右部内侧插入

过程:

  1. 先对subR进行右单旋
  2. 再对parent进行左单旋

void RotateRL(Node* parent)//右左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(subR);
	RotateL(parent);

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

细节:同左右旋


综上所述,旋转的目的保证平衡,同时降低树的高度

三、AVL树的验证

我们主要验证左右子树高度是否平衡,即高度差是否小于等于1

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

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;
}

bool _IsBalance(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}

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

	if (rightH - leftH != root->_bf)
	{
		cout << root->_kv.first << "节点平衡因子异常" << endl;
		return false;
	}

	return abs(rightH - leftH) <= 1
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

细节:

  1. 为了方便计算高度,写一个Height函数
  2. 在子函数递归中,计算高度差是否小于等于1
  3. 与此同时,还要检查平衡因子是否正常

四、AVL树的性能

4.1 优势

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)

4.2 缺陷

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

4.3 适用场景

因此,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。


真诚点赞,手有余香

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1538689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python文学名著分享系统的设计与实现flask-django-nodejs-php

在此基础上&#xff0c;结合现有文学名著分享体系的特点&#xff0c;运用新技术&#xff0c;构建了以python为基础的文学名著分享信息化管理体系。首先&#xff0c;以需求为依据&#xff0c;根据需求分析结果进行了系统的设计&#xff0c;并将其划分为管理员和用户二种角色和多…

自己的博客阵地(互联网核心之一 DNS)

DNS(Domain Name System)是一种用于将易记的域名映射到计算机网络中的 IP 地址的系统。它充当互联网上计算机和网络设备的地址簿&#xff0c;使人们可以使用便于记忆的域名来访问网站&#xff0c;而不必记住复杂的数字IP地址。&#xff08;完全合格域名&#xff09; 名字号联系…

Milvus 向量数据库介绍及使用

一、Milvus 介绍及安装 Milvus 于 2019 年创建&#xff0c;其目标只有一个&#xff1a;存储、索引和管理由深度神经网络和其他机器学习 (ML) 模型生成的大量嵌入向量。它具备高可用、高性能、易拓展的特点&#xff0c;用于海量向量数据的实时召回。 作为专门为处理输入向量查…

题目:异或森林(蓝桥OJ 3400)

问题描述&#xff1a; 解题思路&#xff1a; 一个数也可以看作是一段区间&#xff0c;当该区间的异或和为完全平方数时则符合题意。 我们需要注意枚举的完全平方的上限。 异或前缀和减小时间复杂度。 题解&#xff1a; #include <bits/stdc.h> using namespace std; usi…

MATLAB中的数学建模:基础知识、实例与方法论

前言 在当今科技高速发展的时代&#xff0c;数学建模成为了解析复杂世界的关键工具&#xff0c;而MATLAB作为一种专业的科学计算软件&#xff0c;为我们提供了强大的数学建模平台。MATLAB不仅仅是Matrix Laboratory的简称&#xff0c;更是一个集数值分析、矩阵计算、算法开发和…

如何做好软件架构

最近学习了Udemy的一个软件架构课程&#xff0c;在此做一个记录和分享。 总的来说&#xff0c;软件架构是基于实际业务需求&#xff0c;无法为实际业务服务&#xff0c;再花哨的软件架构都无法产生任何价值。 当需求到来&#xff0c;我们需要分以下几个大致步骤进行分析和拆解…

【项目设计】仿 muduo 库实现 OneThreadOneEventLoop 式并发服务器

文章目录 一、项目介绍1. 项目简介2. 开发环境3. 核心技术4. 开发阶段 二、前置知识了解1. reactor2. timerfd3. timerwheel4. eventfd5. regex6. any 三、框架设计1. 项目模块划分1.1 SERVER 模块1.2 协议模块 2. 项目模块关系图2.1 Connection 模块关系图2.2 Acceptor 模块关…

抖音视频关键词批量采集工具|视频无水印爬虫下载软件

抖音视频关键词批量采集工具&#xff1a; 最新推出的抖音视频关键词批量采集工具&#xff0c;为您提供一种便捷的方式通过关键词搜索和提取符合条件的视频内容。以下是详细的操作说明和功能解析&#xff1a; 操作说明&#xff1a; 进入软件后&#xff0c;在第一个选项卡页面…

Linux centos7安装nginx-1.24.0并且实现自启动

1.安装之前的操作 ps -ef|grep nginx 查看是否有运行 如果有就杀掉 kill -9 pid find / -name nginx 查看nginx文件 rm -rf file /usr/local/nginx* 通通删掉删掉 yum remove nginx 限载一下服务 1.2.下载安装包 地址 nginx: download 2.减压文件 tar…

shell常用通配符

目录 介绍 示例 * ? [a,b,...] / [ab...] [^a,b,...] / [^ab...] [x1-x2] {"xxx","xxx","xxx",...} {x1..x2} 介绍 示例 * 匹配0或多个字符 ls的-d选项可以只列出当前目录下的文件,而不会列出他们包含的内容: ? 只匹配任意一个字符 …

JAVA_Tomcat

Tomcat 使用教程 1.下载: http://tomcat.apache.org/ 2.安装: 解压压缩包(安装目录不要有中文) 3.卸载: 删除目录即可 4.启动: 运行./bin/startup.sh1.黑窗口一闪而过: 没有配置好JDK环境变量2.启动报错(查看日志文件): 端口占用 5.关闭: 1.强制关闭: 点击窗口关闭按钮2.正常…

Vue3 大量赋值导致reactive响应丢失问题

问题阐述 如上图所示&#xff0c;我定义了响应式对象arrreactive({data:[]})&#xff0c;尝试将indexedDB两千条数据一口气赋值给arr.data。但事与愿违&#xff0c;页面上的{{}}在展示先前数组的三秒后变为空。 问题探究 vue3的响应应该与console.log有异曲同工之妙&#xff0…

js教程(8)

一、事件流 1.概述 在JavaScript中&#xff0c;事件流描述的是事件在DOM结构中传播和被处理的顺序。事件流分为冒泡阶段和捕获阶段。 冒泡阶段&#xff08;Bubbling Phase&#xff09;&#xff1a;事件首先从最内层的元素开始向父级元素传播&#xff0c;一直传播到最外层的元素…

分布式Raft原理详解,从不同角色视角分析相关状态

分布式Raft原理详解&#xff0c;从不同角色视角分析相关状态 1. CAP定理2.Raft 要解决的问题3. Raft的核心逻辑3.1. Raft的核心逻辑2.1. 复制状态机2.2. 任期 Term2.3. 任期的意义&#xff1a;逻辑时钟2.4 选举定时器 3. Leader选举逻辑4. 从节点视角查看Leader选举4.1. Follow…

备考ICA----Istio实验7---故障注入 Fault Injection 实验

备考ICA----Istio实验7—故障注入 Fault Injection 实验 Istio 的故障注入用于模拟应用程序中的故障现象&#xff0c;以测试应用程序的故障恢复能力。故障注入有两种: 1.delay延迟注入 2.abort中止注入 1. 环境准备 kubectl apply -f istio/samples/bookinfo/platform/kube/…

计算机二级总结

1.结构体改值&#xff1a; ②第二套1 对形参b所指结构体的数据进行修改 1.结构体传参传地址&结构体 2.在函数里用->,在main里用.&#xff0c;数组用. ②第五套1 对形参b所指结构体的数据进行修改 返回指针变量的地址&#xff0c;不用取*&#xff0c;所以用struct stud…

唯众物联网安装调试员实训平台物联网一体化教学实训室项目交付山东技师学院

近日&#xff0c;山东技师学院物联网安装调试员实训平台及物联网一体化教学实训室采购项目已顺利完成交付并投入使用&#xff0c;标志着学院在物联网技术教学与实践应用方面迈出了坚实的一步。 山东技师学院作为国内知名的技师培养摇篮&#xff0c;一直以来致力于为社会培养高…

二叉树的层次遍历经典问题-算法通关村

二叉树的层次遍历经典问题-算法通关村 1 层次遍历简介 广度优先在面试里出现的频率非常高&#xff0c;整体属于简单题。广度优先又叫层次遍历&#xff0c;基本过程如下&#xff1a; 层次遍历就是从根节点开始&#xff0c;先访问根节点下面一层全部元素&#xff0c;再访问之后…

阿里云OSS分布式存储

目录 &#x1f9c2;1.OSS开通 &#x1f32d;2.头像上传整合OSS &#x1f68d;2.1.引入依赖 &#x1f68d;2.2添加配置 &#x1f68d;2.3创建配置类 &#x1f68d;2.4添加实现类 &#x1f68d;2.5controller调用接口 &#x1f68d;2.6postman测试 1.OSS开通 1.登…

Apache James数据库存储用户信息的密码加密问题

项目场景 Apache James邮件服务器使用数据库来存储用户信息的密码加密问题&#xff1a; 将James的用户改为数据库存储James密码是如何加密验证的 1.将James的用户改为数据库存储 1、修改存储方式 找到james-2.3.2\apps\james\SAR-INF\config.xml 找到<users-store>标…