手把手教你实现AVL树、平衡二叉树

2023-10-29

今天,小编带大家一起来学习平衡二叉树(AVL树)吧。以下就简称AVL树了。

 

想必能点开这篇博客的朋友都是极度深爱计算机的,那今天就让我们一起揭开AVL树的神秘面纱吧!

目录

一.基本概念

二.实现原理

(一)右旋转

(二)左旋转

(三)整体思路

(四)数据插入左子树

①插入左子树左子节点

②插入左子树右子节点

(五)数据插入右子树

①插入右子树右子节点

②插入右子树左子节点

 (六)思维导图

三.代码实现


一.基本概念

AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis。

AVL树是搜索二叉树的一种特殊形式。本质上还是一棵搜索二叉树,遵循着左小右大的原则。

但与普通搜索二叉树不同的是,它每个节点的左右子树高度差都不超过1

为什么要这么设计呢? 我们举个例子便理解了。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

这里要是进行查找65这个数的时候,我们需要一遍遍的递归,直到这颗树的树底。但这样一棵树递归次数太多,高度低还好,如果一棵树太高,就有可能造成递归次数太多,引起栈溢出。 

就算不会引起栈溢出,这种树有多高就递归几次的方式,效率也是极低的。

所以我们需要在保持左小右大的前提下,让树的高度尽可能的低,达到一种节点的左右子树高度差不大于1的效果,即是AVL树。

那以上图为例,AVL树就是这样:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 这里,我们就能看出来了,要是找到65这个数子,我们只需要递归2次,而原树则需要5次。

 所以,AVL树可以理解为是搜索二叉树的优化版本。

二.实现原理

我们首先要知道节点怎么左旋转和右旋转。

(一)右旋转

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 以上图为例,将L右旋转的话,就是L的右子树变成A,L原来的右子树变成A的左子树即可

(二)左旋转

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

与右旋转相反,将L左旋转,就是L左子树变成A,L原来的左子树变成A的右子树。 

(三)整体思路

既然要实现AVL树,那么我们就需要用到几个参数。

1.struct AVLTree  ,这是AVL树的结构体,结构体里除了装数据的val和连接左右子树的指针外,还应该有一个用来判断该节点左右子树高度差的参数bf。bf参数设为int类型,-1代表左子树高,0代表树一般高,1代表右子树高。同时也应注意:结构体的每个节点都应该是指针类型的,所以在一开始定义结构体变量的时候,就要定义一个指针类型的结构体

2.bool judge_hight,这是一个bool类型的参数,用来判断这一次插入节点后,该节点的子树是否有长高的。judge_hight 初始化为true即可,因为第一次插入数据,树一定会长高。

下面,我们就可以创建AVL树了!

函数声明:void AVLTree_Insert(AVLTree* at, bool judge_hight);

AVL树元素的插入方式与搜索二叉树大体类似,但有些许不同:

假设我们现在有一个数据,按照左小右大的规则插入搜索二叉树后,当找到一个位置插入数据后,我们需要判断树是否长高了,这里就需要分为两大类了:

插入左子树和插入右子树。

(四)数据插入左子树

如果数据是插入了节点a的左子树导致长高,那么我们就需要判断a的左右子树高度差(bf)这里分为三种情况:

bf = 0 左右子树平衡,将bf改为1即可。

bf = -1 右边高, 加入左节点,树就平衡了,bf改为0,judge_hight置为false

bf = 1 左边高,需要调用函数来判断该节点a是否还平衡,之后judge_hight置为false。设调用的函数为:void Treebalance_left(AVLTree* at)。在这个函数里,我们就需要继续判断是插入了l的左节点还是右节点。

①插入左子树左子节点

插入左子节点的含义是:节点插入a的左子树后,使a不再平衡,原因是a的左子树的左边变高

以下三种均为插入左子节点的情况,这三种看似不同,其实一致,均是l的左边高。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

图上看已经很清楚了, a的已经不平衡了,我们就需要进行右旋转a的左子节点来使a平衡。

相当于直接把l提到a的位置

旋转后结果如图:

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 这里可以看出,a的bf改为0,l的bf改为0

②插入左子树右子节点

与插入左子节点相反,插入右子节点是指:节点a不平衡是因为左边高,而a左边高是因为a的左子节点的右边高。也就是在a的左子节点插入了右子树

同理,以下三种均为插入右子节点的情况,即a不平衡,l的右边高。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 对于a而言,这种也是不平衡的情况,那么我们需要先对l进行左旋转,再对n进行右旋转

这里值得注意的是:l左旋转后n右旋转,其实就是使n先转到l的位置,再转到a的位置。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

第一步:l左旋转(n转到l位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16  

第二步:n右旋转(n转到a位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 这里我们需要注意的是:此时a与l的bf值三棵树并不相同。为什么呢,因为一开始的时候三棵树n的左右子树高度并不相同。

下面给出n的bf值与a、l的bf值的规律:

第一棵 第二棵 第三棵
n的BF值 0 1 -1
a和l的BF值 a=0   l=0 a=-1   l=0 a=0   l=1

我们需要注意的是,三棵树的旋转方式一样,但BF值不同,所以在代码里要先判断n的BF值再旋转。 

(五)数据插入右子树

数据插入右子树,我们也需要判断此时节点的平衡情况,再做下一步打算。同样的,也是先判断插入数据前节点的bf值来判断,也分三种情况:

BF=0:插入右子树后BF=-1

BF=1:插入右子树后,节点平衡,BF=0,judge_hight置为false。

BF=-1:插入右子树后节点可能不平衡,需要调用函数判断一下。之后judge_hight置为false。

可以设调用的函数为void Treebalance_right(AVLTree* at);这个函数里,我们要传入该节点的右子树,判断数据是插入它的左子节点还是右子节点。

①插入右子树右子节点

以下三棵树均为插入右子节点的情况。三棵树平衡方式一样,均为l左旋转,使l转到a的位置。 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 l左旋转后:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

②插入右子树左子节点

以下三棵树均为插入左子节点的情况,旋转方式均为l右旋转后n左旋转,即n先跳到l位置后跳到a位置

但是与插入左子树右子节点类似,要先判断n的BF值,写出相应的a与l旋转后的BF值再旋转。这里小编先给大家旋转完再判断BF值。(代码里一定要先判断!)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 l右旋转(n跳到l的位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

 n左旋转(n跳到a的位置)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

到这里,n的BF值与a、l的BF值的关系也就出来了。 

n的BF值 0 1  -1
a的BF值 0 0 1
l的BF值 0  -1 0

 (六)思维导图

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCx6KaBIOWuheWcqOWutg==,size_20,color_FFFFFF,t_70,g_se,x_16

三.代码实现

#define LH 1//左树高
#define NH 0//一般高
#define RH -1//右树高
typedef int AVLTreetype;
typedef struct Tree
{
	AVLTreetype val;
	struct Tree* leftchild, * rightchild;
	int bf;//此节点子树的高度差
}*AVLTree, Tree;
void AVLTree_Insert(AVLTree& at, AVLTreetype n, bool& judge_hight);//插入AVL树
void AVLTreebalance_left(AVLTree& at);//判断左树是否平衡
void AVLTreebalance_right(AVLTree& at);//判断右树是否平衡
void L_Roll(AVLTree& at);//左旋
void R_Roll(AVLTree& at);//右旋
void AVLTree_Insert(AVLTree& at, AVLTreetype n, bool& judge_hight)//插入
{
	if (at == NULL)
	{
		AVLTreeinit(at, n);
		judge_hight = true;
		return;
	}
	else
	{
		if (n == at->val)
		{
			cout << "\nHave Insert:" << n << endl;
			judge_hight = false;
			return;
		}
		else if (n < at->val)
		{
			if (at->leftchild)
			{
				AVLTree_Insert(at->leftchild, n, judge_hight);//递归
			}
			else// at->leftchild == NULL
			{
				AVLTreeinit(at->leftchild, n);
				judge_hight = true;
			}
			if (judge_hight)
			{
				switch (at->bf)
				{
				case LH:
					judge_hight = false;
					AVLTreebalance_left(at);//判断是否平衡
					break;
				case NH:
					at->bf = LH;
					break;
				case RH:
					at->bf = NH;
					judge_hight = false;
					break;
				}
			}
		}
		else
		{
			if (at->rightchild)
			{
				AVLTree_Insert(at->rightchild, n, judge_hight);
			}
			else// at->rightchild == NULL
			{
				AVLTreeinit(at->rightchild, n);
				judge_hight = true;
			}
			if (judge_hight)
			{
				switch (at->bf)
				{
				case RH:
					judge_hight = false;
					AVLTreebalance_right(at);
					break;
				case NH:
					at->bf = RH;
					break;
				case LH:
					at->bf = NH;
					judge_hight = false;
					break;
				}
			}
		}
	}
}
void AVLTreebalance_left(AVLTree& at)
{
	AVLTree l = at->leftchild, lr = l->rightchild;
	switch (l->bf)
	{
	case LH:
		at->bf = l->bf = NH;
		R_Roll(at);
		break;
	case RH:
		switch (lr->bf)
		{
		case LH:
			at->bf = RH;
			l->bf = NH;
			break;
		case NH:
			at->bf = l->bf = NH;
			break;
		case RH:
			at->bf = NH;
			l->bf = LH;
			break;
		}
		L_Roll(at->leftchild);
		R_Roll(at);
		break;
	}
}
void AVLTreebalance_right(AVLTree& at)//判断树右边是否平衡
{
	AVLTree r = at->rightchild, rl = r->leftchild;
	switch (r->bf)
	{
	case RH:
		at->bf = r->bf = NH;/
		L_Roll(at);
		break;
	case LH:
		switch (rl->bf)
		{
		case LH:
			at->bf = NH;
			r->bf = RH;
			break;
		case NH:
			at->bf = r->bf = NH;
			break;
		case RH:
			at->bf = LH;
			r->bf = NH;
			break;
		}
		R_Roll(at->rightchild);
		L_Roll(at);
		break;
	}
}
void L_Roll(AVLTree& at)//左旋
{
	AVLTree r = at->rightchild;
	at->rightchild = r->leftchild;
	r->leftchild = at;
	at = r;
}
void R_Roll(AVLTree& at)//右旋
{
	AVLTree l = at->leftchild;
	at->leftchild = l->rightchild;
	l->rightchild = at;
	at = l;
}

小编呕心沥血之作,恳请点赞关注支持一下吧~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你实现AVL树、平衡二叉树 的相关文章

  • ASP.NET MVC 中的经典 ASP (C#)

    我有一个应用程序想要 最终 转换为 ASP NET MVC 我想要进行全面的服务升级 到 ASP NET 但想要使用当前的 ASP 内容来运行当前的功能 这样我就可以在对新框架进行增量升级的同时升级小部分 该站点严重依赖于不太成熟的 VB6
  • OpenCv读/写视频色差

    我试图简单地使用 openCV 打开视频 处理帧并将处理后的帧写入新的视频文件 我的问题是 即使我根本不处理帧 只是打开视频 使用 VideoCapture 读取帧并使用 VideoWriter 将它们写入新文件 输出文件看起来比输入更 绿
  • 为什么我不能用 `= delete;` 声明纯虚函数?

    Intro 纯虚函数使用通用语法声明 virtual f 0 然而 自 c 11 以来 有一种方法可以显式地传达non existence 特殊 成员函数的 Mystruct delete eg default constructor Q
  • 使用post方法将多个参数发送到asp.net core 3 mvc操作

    使用 http post 方法向 asp net mvc core 3 操作发送具有多个参数的 ajax 请求时存在问题 参数不绑定 在 dot net 框架 asp net web api 中存在类似的限制 但在 asp net mvc
  • C++:重写已弃用的虚拟方法时出现弃用警告

    我有一个纯虚拟类 它有一个纯虚拟方法 应该是const 但不幸的是不是 该接口位于库中 并且该类由单独项目中的其他几个类继承 我正在尝试使用这个方法const不会破坏兼容性 至少在一段时间内 但我找不到在非常量方法重载时产生警告的方法 以下
  • Clang 编译器 (x86):80 位长双精度

    我正在尝试在 x86 Windows 平台上使用本机 80 位长双精度 海湾合作委员会选项 mlong double 80 https gcc gnu org onlinedocs gcc x86 Options html似乎不适用于 cl
  • JSON 数组到 C# 列表

    如何将这个简单的 JSON 字符串反序列化为 C 中的列表 on4ThnU7 n71YZYVKD CVfSpM2W 10kQotV 这样 List
  • 从多个类访问串行端口

    我正在尝试使用串行端口在 arduino 和 C 程序之间进行通信 我对 C 编程有点陌生 该程序有多种用户控制形式 每一个都需要访问串口来发送数据 我需要做的就是从每个类的主窗体中写入串行端口 我了解如何设置和写入串行端口 这是我的 Fo
  • 访问者和模板化虚拟方法

    在一个典型的实现中Visitor模式 该类必须考虑基类的所有变体 后代 在许多情况下 访问者中的相同方法内容应用于不同的方法 在这种情况下 模板化的虚拟方法是理想的选择 但目前这是不允许的 那么 模板化方法可以用来解析父类的虚方法吗 鉴于
  • 当前的 c++ 工作草案与当前标准有何不同

    通过搜索该标准的 PDF 版本 我最终找到了这个链接C 标准措辞草案 http www open std org jtc1 sc22 wg21 docs papers 2012 n3376 pdf从 2011 年开始 我意识到我可以购买最终
  • 基于xsd模式生成xml(使用.NET)

    我想根据我的 xsd 架构 cap xsd 生成 xml 文件 我找到了这篇文章并按照说明进行操作 使用 XSD 文件生成 XML 文件 https stackoverflow com questions 6530424 generatin
  • C# 中条件编译符号的编译时检查(参见示例)?

    在 C C 中你可以这样做 define IN USE 1 define NOT IN USE 1 define USING system 1 system 1 IN USE 进而 define MY SYSTEM IN USE if US
  • g++ 对于看似不相关的变量“警告:迭代...调用未定义的行为”

    考虑以下代码strange cpp include
  • 有没有一种简单的方法可以让 Visual Studio 2015 使用特定的 ToolsVersion?

    使用特定版本构建项目或解决方案时msbuild我可以使用以下命令选择早期的 net 工具链 toolsversion or tv switch C Program Files x86 MSBuild 14 0 bin msbuild tv
  • 在类的所有方法之前运行一个方法

    在 C 3 或 4 中可以做到这一点吗 也许有一些反思 class Magic RunBeforeAll public void BaseMethod runs BaseMethod before being executed public
  • 结构体指针的动态数组

    我必须使用以下代码块来完成学校作业 严格不进行任何修改 typedef struct char firstName char lastName int id float mark pStudentRecord pStudentRecord
  • 运算符“==”不能应用于“int”和“string”类型的操作数

    我正在编写一个程序 我想到了一个数字 然后计算机猜测了它 我一边尝试一边测试它 但我不断收到不应该出现的错误 错误是主题标题 我使用 Int Parse 来转换我的字符串 但我不知道为什么会收到错误 我知道它说 不能与整数一起使用 但我在网
  • 使用 C# 从 DateTime 获取日期

    愚蠢的问题 给定日期时间中的日期 我知道它是星期二 例如我如何知道它的 tue 2 和 mon 1 等 Thanks 您正在寻找星期几 http msdn microsoft com en us library system datetim
  • WinRT 定时注销

    我正在开发一个 WinRT 应用程序 要求之一是应用程序应具有 定时注销 功能 这意味着在任何屏幕上 如果应用程序空闲了 10 分钟 应用程序应该注销并导航回主屏幕 显然 执行此操作的强力方法是在每个页面的每个网格上连接指针按下事件 并在触
  • 使用 Crypto++ 获取 ECDSA 签名

    我必须使用 Crypto 在变量中获取 ECDSA 签名 我在启动 SignMessage 后尝试获取它 但签名为空 我怎样才能得到它 你看过 Crypto wiki 吗 上面有很多东西椭圆曲线数字签名算法 http www cryptop

随机推荐

  • SVD实现数字水印

    SVD方法的基本原理是将水印嵌入到原始图像的奇异值中 具体流程如下 1 设输入图像为mxn矩阵A 对其进行SVD分解 2 设水印图像为mxn矩阵W 嵌入到到原图像奇异值中S aW a为加权系数 对其进行SVD分解的到含有水印的奇异值 3 用
  • Nginx SSL模块配置提供HTTPS支持(Ngx_http_ssl_module)

    Ngx http ssl module 此模块为Nginx提供HTTPS支持 官方文档 http nginx org en docs http ngx http ssl module html 相关指令 ssl on off SSL功能启用
  • 作为一个上班族,靠Python副业兼职,也能月入1W+

    不知道大家从事的是IT行业还是其他行业 想通过Python兼职首先就需要掌握这项专业技能 如果有Python技术基础 那能兼职的项目可就多了 我靠Python做兼职已经有三四年了 见过身边很多朋友同事陆续学Python 有学成的 也有中途放
  • python错误与异常、调试

    错误 语法错误 逻辑错误 系统错误 异常 程序执行过程中出现的未知错误 语法和逻辑都是正常的 程序业务逻辑不完善引起的程序漏洞 错误 与 异常的区别 异常可以被捕获和处理 错误一般是编码错误 逻辑错误 系统错误 常见的异常类型 除零类型 名
  • vue环境配置文件

    配置文件 在vue项目目录下 我们可以看到诸如package json gitignore package lock json等等能配置项目的结构 引用的库 运行的方式 版本控制等等的都称为配置文件 2 环境配置文件就是能根据项目运行的环境
  • MATLAB数字图像处理(三)——图像轮廓提取与边缘检测

    文章目录 二值图像轮廓提取 灰度图像边缘检测 含噪图像边缘检测 均值滤波函数 二值图像轮廓提取 根据掏空内部点算法 运用Matlab编程实现二值图像的轮廓提取 以二值图像circles为例 I imread circles png subp
  • 几何变换详解

    几何变换详解 在三维图形学中 几何变换大致分为三种 平移变换 Translation 缩放变换 Scaling 旋转变换 Rotation 以下讨论皆针对DirectX 所以使用左手坐标系 平移变换 将三维空间中的一个点 x y z 1 移
  • Centos7更新glibc2.18

    Centos7更新glibc2 18 查看glibc版本 下载解压glibc2 18 编译安装 结果验证 查看glibc版本 查看glibc版本 ldd version 下载解压glibc2 18 参考 https blog csdn ne
  • 记录使用ESP32做WiFi模块使用的学习

    这里使用ESP32作为WiFi模块 使用STA模式或者AP模式 目录 前言 二 配置WiFi模式 1 STA模式 2 AP模式 3 AP STA模式 三 实现ESP32与电脑端通信 ESP32的数据接收与传输 ESP32的完整代码 前言 因
  • cephadm快速部署指定版本ceph集群

    官方文档 https docs ceph com en pacific 1 虚拟机规划 主机名 IP 角色 ceph1 192 168 150 120 cephadm mon mgr osd ceph2 192 168 150 121 mo
  • 四大主流芯片架构(X86、ARM、RISC-V和MIPS)

    目前市场上主流的芯片架构有 X86 ARM RISC V和MIPS四种 序号 架构 特点 代表性的厂商 运营机构 发明时间 1 X86 性能高 速度快 兼容性好 英特尔 AMD 英特尔 1978年 2 ARM 成本低 低功耗 苹果 谷歌 I
  • STM32-WWDG窗口看门狗-库函数版本

    参考资料 1 正点原子探索者STM32f407开发板 STM32f407开发指南 库函数版本 第12章 2 STM32F4xx 官方参考资料 STM32F4xx中文参考手册 第19章 目录 WWDG时钟 产生RESET的原理 超时值计算公式
  • unity与android交互独立jar不依附于主activity和manifest

    我们一般android与unity交互是android建立一个主activity继承unityplayactivity然后出jar 然后出一个manifest 那么问题来了 这样一个jar只能适应一个项目 现在plugins下面已经有三方的
  • 【计算机视觉

    文章目录 一 分割 语义相关 5篇 1 1 SAMUS Adapting Segment Anything Model for Clinically Friendly and Generalizable Ultrasound Image S
  • 如何激活conda的环境呢

    要激活 conda 环境 需要在命令行或终端中输入以下命令 condaactivate lt 环境名称 gt 其中 lt 环境名称 gt 是你要激活的 conda 环境的名称 例如 如果要激活名为 myenv 的 conda 环境 则需要输
  • CMD常用的DOS命令

    一 CMD的打开方式 1 开始 系统 命令提示符 2 win R 输入cmd 3 在任意文件夹下按住shift键 鼠标右击 在此处打开命令行 4 资源管理器前面打上cmd 空格 二 管理员方式运行 1 开始 系统 命令提示符 右击管理员权限
  • 如何无代码快速制作AR特效和滤镜?Lens Studio官方案例详解之Paper Head

    我首先在这个网页看了一下Lens Studio的总体介绍 然后想跟着Templates提供的模板快速上手 其中第一个模板就是Paper Head 但是我发现 模板看着简单 但是其背后的很多概念 逻辑还是搞不太清的 所以可能还是要去看文档 但
  • 协方差矩阵的matlab计算

    在统计学与概率论中 协方差矩阵是一个矩阵 其每个元素是各个向量元素之间的协方差 Wiki 协方差矩阵的计算以列向量为单位 是列向量各元素之间的关系的表达 定义为 一个列向量也叫做一个样本向量 列向量中的元素代表样本 列向量中的元素的个数的和
  • 六大软件开发模型详解

    软件测试工作与软件开发模型息息相关 在不同的软件开发模型中 测试的任务和作用也不相同 因此测试人员要充分了解软件开发模型 以便找准自己在其中的定位与任务 软件开发模型规定了软件开发应遵循的步骤 是软件开发的导航图 它能够清晰 直观地表达软件
  • 手把手教你实现AVL树、平衡二叉树

    今天 小编带大家一起来学习平衡二叉树 AVL树 吧 以下就简称AVL树了 想必能点开这篇博客的朋友都是极度深爱计算机的 那今天就让我们一起揭开AVL树的神秘面纱吧 目录 一 基本概念 二 实现原理 一 右旋转 二 左旋转 三 整体思路 四