AVL树 c语言版本 插入部分

news2024/12/25 1:32:20

目录

引入平衡树

为什么要变平衡

怎么判断是否需要变平衡

怎么变平衡

LL型失衡

RR型失衡

LR型失衡

RL型失衡

补充

左旋补充

右旋补充

Code

开辟一个新节点

 初始化

获取树的高度

左旋函数

更新树高

树高的求法

右旋转函数

插入

InsertNode()

更新树高

getbalance()

根据balance判断是否失衡

RL型失衡​编辑如图(f-1)所示:

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

测试函数

perOrder()

midOrder()

Find()

测试

代码托管


引入平衡树

假设我们有两个节点:当我们插入第三个节点,就失衡了:此刻我们就要把它平衡一下。

为什么要变平衡

为什么说它失衡了呢,又为什么要把它变平衡?

如图a,假设我们要查找30这个节点就要查3次才能找到

但是如果平衡之后(图b)就只需要2次就可以找到了,这样可以提高效率,这两个图不够明显,如果是100个节点的图就够明显了。

怎么判断是否需要变平衡

我们引入 平衡因子的概念

平衡因子 = 根节点的 左孩子高度 -  右孩子高度

当 平衡因子的绝对值 > 1 时,我们认为这个树是失衡的,需要变为平衡。

如图(c-1):

 根节点( 10 ) 的左孩子高度(0) - 右孩子高度(2)= 平衡因子 (-2)

| -2 | > 1,即该数树为失衡的树。

怎么变平衡

首先,假如有这样一个二叉搜索树:

LL型失衡

我们应该怎么平衡呢?

首先,因为根节点最小,所以把根节点变到2的右边,这也符合二叉搜索的特性,即:小了往左走

这个时候20就成了新的根节点,而原先的根节点10就成了现在根节点(20)的左孩子节点。

因此对于这种一边倒的二叉搜索树,并且是向左倒的

 也就是要往左边分点节点,我们才能保持它的平衡,

对于LL型失衡,我们可以总结有如下 左旋定理:

RR型失衡

同理,有往左边倒的就有往右边倒的,如下图:

 因为40比30大的缘故,我们可以让40到30的右边去,这样就平衡了,并且符合二叉搜索树的特性,即:大了就往右边走:

此时30就是新节点,40就为30的右孩子。

这种往右边一边倒的失衡,我们称之为RR型失衡。

对此,我们可以总结如下右旋定理:

LR型失衡

因为root节点的左孩子的右节点造成的失衡就叫LR型失衡,如图 (b-1):

因为是由于右节点造成的失衡,所以我们就让右节点到左节点去,可以用左旋定理

变为图 (b-2):

 图(b-2)我们看它的平衡因子,大于1,所以还要再变。

又因为图(b-2)是RR型失衡,所以套用  右旋定理

变为图(b-3):

此时平衡因子<1,是AVL树,因此不再变。

流程图:

RL型失衡

如图(a-1),因为root节点的右孩子的左节点造成的失衡就叫RL型失衡:

对于LR型失衡,因为是左节点造成的失衡,所以要先采用右旋定理把右节点变到左边去:

变为图(a-2):

此时平衡因子为 -2 ,仍然不平衡还需要再变

此时因为向左倾斜,即要向左部分分点,这样才平衡要调用  左旋定理

变为图 (a-3):此时平衡因子 <1,即不用再变。

流程图:

因此,我们可以只有总结:

补充

左旋补充

有如下图:

 此时应该先左旋,然后再按二叉搜索树的特性再排

旧根节点10比  新根节点原先的左子树 19小,所以19应该排到10右边:

因此,左旋定理可以得到补充,如下:

右旋补充

有图(d-1):此时应该先右旋,再按二叉搜索树特性排:

 因此,右旋定理可以得到如下补充:

Code

首先写一下树的结构

val left right是必须的,又新加了一个height,用来求平衡因子用:

typedef  struct Node
{
	int val;//数据域
	int height; //树高
	struct Node* left;
	struct Node* right;
}Node;

开辟一个新节点


Node* newnode = (Node*)malloc(sizeof(Node));

 初始化

node->val=val;
node->height = 1;
node->left = NULL;
node->right = NULL;

 开辟和初始化我们都封装起来:

Node* newnode(int val)
{
Node* node = (Node*)malloc(sizeof(Node));
node->val=val;
node->height = 1;  //只有一个节点时,树高为1
node->left = NULL;
node->right = NULL;

retur node;
}

获取树的高度

如果树高度为空,返回0

否则就把树的高度返回。

//获取树的高度
int getHeight(Node* node)
{
	if (node->height == NULL)
		return 0;
	else
		return node->height;
}

接下来写左旋函数,右旋函数

左旋函数

左旋函数先按左旋定理写:

int LeftRtate(Node* root)
{
	//新节点为旧节点的右子树
	Node* newroot = root->right;

	//T2时新根节点的左子树
	Node* T2 = newroot->left;

}

 开始旋转:




开始旋转:
	//旧根节点变为新根节点的左子树
	newroot->left=root;

	//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树
	root->right = T2;

更新树高

经过左旋之后root和newroot的高度都变了,我们需要更新一下。

怎么更改呢?首先需要先求树高。

树高的求法

拿图(b-1)举例:

 图(b-1)的树高为3,其中,左子树的树高为2,右子树的树高为0。

我们取左右子树最大的树高+1就是整个树的树高:

int max(int A,int B)
{
	if (A > B ? A : B);

}
int LeftRtate(Node* root)
{
	//新节点为旧节点的右子树
	Node* newroot = root->right;

	//T2时新根节点的左子树
	Node* T2 = newroot->left;

	//旧根节点变为新根节点的左子树
	newroot->left=root;

	//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树
	root->right = T2;

	//树高
 root->height = 1+max(getHeight(root->left),getHeight(root->right));
 newroot->height = 1+max(getHeight(newroot->left), getHeight(newroot->right));

 return newroot;//返回新根节点

右旋转函数

同样的道理,按照右旋定理就可以了:

Node* RightRatate(Node* root)
{
	//新根节点为原先旧节点的左子树
	Node* newroot = root->left;

	//新根节点的右子树
	Node* T2 = newroot->right;

	//开始旋转
	//旧根节点变为新根节点的右子树
	newroot->right = root;

	//3.如果新根节点原来有右子树,那么新根节点的右子树就会作为旧根节点的左子树
	root->left = T2;
	//树高
	root->height = 1 + max(getHeight(root->left), getHeight(root->right));
	newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));

	return newroot;
}

接下来就是重头戏

插入

首先按照搜二叉的方式插入,

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边。

插入完之后可能会造成失衡。

怎么判断失衡呢?根据平衡因子。

然后再去按照时哪种失衡,进行对应的调整。

欧克,一步一步来,首先插入

InsertNode()

//插入
Node* InsertNode(Node* node,int key)
{
	
	

}

如果当前节点为空,那么就调用我们的newnode()函数,malloc了一个节点并且对节点进行初始化


Node* InsertNode(Node* node,int key)
{
	
	if (node==NULL)return newnode(key);

}

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边:

Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)(node->left = InsertNode(node->right, key));
	if (key < node->val)(node->left = InsertNode(node->left, key));
	
}

接下来就是用平衡因子判断插入之后是否失衡了。

写一个求平衡因子的函数:

但是在此之前需要先更新一下树高,因为平衡因子是根据树高求出来的。

更新树高

树高的求法:左右子树树高最大的那一个+1。

Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)(node->left = InsertNode(node->right, key));
	if (key < node->val)(node->left = InsertNode(node->left, key));
	
	//更新树高
	//因为root改变了,所以它俩的树高需要更新
	node->height = 1 + max(getHeight(node->left), getHeight(node->right));
}

getbalance()

平衡因子的求法在刚开始就说过,就是左子树的树高-右子树的树高。


int getbalance(Node* node)
{
	return getHeight(node->left)- getHeight(node->right);

}

根据balance判断是否失衡

 拿RL型失衡举例:

RL型失衡如图(f-1)所示:

根节点的 balance(平衡因子) =-2   根节点的右子树的balance=1

此时为RL型失衡。

balance<-1 && getbalance(node->right) < 0

RL失衡了就按照我们之前写的那个平衡方法就平衡一下:

先右旋,再左旋

   node->right=RightRatate(node->right);  即node链接右边的节点,让要旋转的节点旋到右边
		return LeftRtate(node);

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

if (balance< -1 && getbalance(node->right)>0)
	{
		//RL失衡
		//需要先右旋,再左旋
       node->right=RightRatate(node->right);
		return LeftRtate(node);
		
	}
	if (balance >1 && getbalance(node->left) <0)
	{
		//LR失衡
		//需要先左旋旋,再右旋
	
		node->left=LeftRtate(node->left);	
		return RightRatate(node);
	}
	if (balance<-1 && getbalance(node->right) < 0)
	{
		//RR型失衡
		//右旋
		return LeftRtate(node);
	}
	if (balance>1 && getbalance(node->right) >0)
	{
		//LL失衡
		//左旋
		return RightRatate(node);
	}

最后把根节点返回就可以了。


	return node;

至此,Insert()就全部写完了,总结为如下:

//插入
Node* InsertNode(Node* node,int key)
{
	if (node==NULL)return newnode(key);
    
	if (key > node->val)
	{
		node->right = InsertNode(node->right, key);
	}
	else if (key < node->val)
	{
		node->left = InsertNode(node->left, key);
	}
	else
	{
		return node;
	}
	
	//更新树高
	//因为root改变了,所以它俩的树高需要更新
	node->height = 1 + max(getHeight(node->left), getHeight(node->right));

	//根据balance判断是否失衡

	int balance = getbalance(node);

	if (balance< -1 && getbalance(node->right)>0)
	{
		//RL失衡
		//需要先右旋,再左旋
       node->right=RightRatate(node->right);
		return LeftRtate(node);
		
	}
	if (balance >1 && getbalance(node->left) <0)
	{
		//LR失衡
		//需要先左旋旋,再右旋
	
		node->left=LeftRtate(node->left);	
		return RightRatate(node);
	}
	if (balance<-1 && getbalance(node->right) < 0)
	{
		//RR型失衡
		//右旋
		return LeftRtate(node);
	}
	if (balance>1 && getbalance(node->right) >0)
	{
		//LL失衡
		//左旋
		return RightRatate(node);
	}

	return node;
}

然后我们来写测试函数

测试函数

我们可以用前序和后续就可以打印看看是否出错了,先写前序

perOrder()


void perOrder(Node* root)
{
	if (root == NULL)return;
	printf("%d ", root->val);
	perOrder(root->left);
	perOrder(root->right);

	
}

midOrder()

void midOrder(Node* root)
{
	if (root == NULL)return;
	midOrder(root->left);
	printf("%d ",root->val);
	midOrder(root->right);
	
}

Find()

find有三个参数

写一个根节点,把根节点赋值给当前节点,让当前节点去遍历。一个count参数统计查找次数。

key为要查找的值。

注意,count类型为int*而不是Int.

因为我们想让count的改变影响我们外面的实参值。

Node* Find(Node* root,int* count,int key)
{

}

如果要查找的值比当前节点小,就往左走,否则如果比当前节点大,就往右走,如果不大不小那就说明要找的就是当前节点,把当前节点return。

如果找了一圈都没有找到,即当前节点跌代到空节点都找不到,那就返回NULL。

注意不是返回false,因为我们的Find()函数还要统计次数的功能,因此不是并没有给它一个bool类型。

Node* Find(Node* root,int* count,int key)
{
	Node* cur = root;
	while (cur)
	{
		if (key> cur->val)
		{
			cur = cur->right;
			(*count)++;
		}
		else if (key > cur->val)
		{
			cur = cur->left;
			(*count)++;
		}
		else
		{
			return cur;
		}
		
	}
	return NULL;
}

测试


void test()
{
	Node* root = NULL;
	root= InsertNode(root,  10);
	root = InsertNode(root, 20);
	root = InsertNode(root, 30);
	root = InsertNode(root, 40);
	root = InsertNode(root, 50);
	root = InsertNode(root, 60);
	root = InsertNode(root, 70);
	

	int count = 0;
	int key = 70;
	Node* result=Find(root, &count, key);
	printf("要找的值是:%d\n",key);
	printf("找了%d 次\n",count);

	printf("前序遍历:_______ _ __  _\n");
	perOrder(root);
	printf("\n中序遍历:_______ _ __  _\n");
	midOrder(root);
}

报错:这是因为我们应该判断node==NULL,而不该去判断node->height;

当node为空时,node->height就是空指针指向height就会报错。

改为如下图:运行:

我们根据前序和中序打印的结果,可以画出这样一个二叉树:

 这个二叉树是一个平衡二叉树,即AVL树,并且找7这个节点找3次就能找到。

而我们打印出来的要找两次是因为我们根节点那次没算,当cur=node也让count++就可以了。

代码托管

AVLTreeinsert.c · 




 

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

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

相关文章

ZZ308 物联网应用与服务赛题第G套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;G卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用…

星岛专栏|从Web3发展看金融与科技的融合之道

11月起&#xff0c;欧科云链与香港主流媒体星岛集团开设Web3.0安全技术专栏&#xff0c;该专栏主要面向香港从业者、交易机构、监管机构输出专业性的安全合规建议&#xff0c;旨在促进香港Web3.0行业向安全与合规发展。 出品&#xff5c;欧科云链研究院 自2016年首届香港金融…

P02项目诊断报警组件(学习操作日志记录、单元测试开发)

★ P02项目诊断报警组件 诊断报警组件的主要功能有&#xff1a; 接收、记录硬件设备上报的报警信息。从预先设定的错误码对照表中找到对应的声光报警和蜂鸣器报警策略&#xff0c;结合当前的报警情况对设备下发报警指示。将报警消息发送到消息队列&#xff0c;由其它组件发送…

Linux常用命令及项目部署

目录 Linux介绍 Linux环境 下载xshell 常见的Linux命令 搭建Java部署环境 1.jdk 2.tomcat 3.mysql 进行部署 Linux介绍 Linux操作系统是和Windows并列的关系&#xff0c;Linux主要通过命令行进行操作的。 Linux环境 1.使用虚拟机&#xff0c;电脑上安装虚拟机软件 …

快来看看如何拿下7+干湿结合生信思路。赶紧拿起笔记本

今天给同学们分享一篇生信文章“CENPF/CDK1 signaling pathway enhances the progression of adrenocortical carcinoma by regulating the G2/M-phase cell cycle”&#xff0c;这篇文章发表在J Transl Med期刊上&#xff0c;影响因子为7.4。 结果解读&#xff1a; CENPF在AC…

Python实现图片与PDF互相转换

目录 图片转PDF文件夹所有图片转为1个PDF文件夹指定图片转为1个PDF文件夹所有图片分别转为PDF举例 PDF转图片指定PDF转为图片文件夹所有PDF转为图片举例 图片转PDF 之前的一篇博客《Python合并拼接图片》&#xff0c;可对图片进行合并拼接 使用前需要安装PyMuPDF库&#xff0c…

lua脚本实现redis分布式锁(脚本解析)

文章目录 lua介绍lua基本语法redis执行lua脚本 - EVAL指令使用lua保证删除原子性 lua介绍 Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。 设…

最适合后端程序员要学的前端基本知识js版本

本文章适合有后端基础&#xff0c;也对前端知识有所了解的同学&#xff0c;简单而精准的了解后端程序员要了解的前端知识。让自己能看懂前端js&#xff0c;html和css版本后续写出。。。。 自定义对象 在对象中创建对象的行为时若是要调用对象属性&#xff0c;要加this 列如&a…

kubernetes 认证授权

目录 一、kubernetes API访问控制 二、pod绑定sa 三、认证 四、授权 一、kubernetes API访问控制 Authentication&#xff08;认证&#xff09;认证方式现共有8种&#xff0c;可以启用一种或多种认证方式&#xff0c;只要有一种认证方式通过&#xff0c;就不再进行其它方式…

【Axure高保真原型】树切换动态面板案例

今天和大家分享树切换动态面板的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击最后一级人物节点&#xff0c;可以切换右侧面板的状态到对应的页面&#xff0c;左侧的树是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表…

人大金仓三大兼容:SQL Server迁移无忧

SQL Server在数据库领域一直占据着重要地位。作为一款成熟稳定的关系型数据库管理系统&#xff0c;SQL Server在国内有着广泛的用户群体&#xff0c;医疗、海关、政务等行业的核心业务系统多采用SQL Server数据库。随着政策与市场的双重驱动&#xff0c;信息技术应用创新产业的…

【快速使用ShardingJDBC的哈希分片策略进行分表】

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f34a;1.引入maven依赖&#x1f34a;2.启动类上添加注解MapperScan&#x1f34a;3.添加application.properties配置&#x1f34a;4.普通的自定义实体类&#x1f34a;5.写个测试类验证一下&#x1f34a;6.控制台打印…

竞赛 目标检测-行人车辆检测流量计数

文章目录 前言1\. 目标检测概况1.1 什么是目标检测&#xff1f;1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 行人车辆目标检测计数系统 …

Linux之make/maakefile

access不是实时更新的。 printf打印并不是直接给屏幕而是先放到缓冲区。 可以通过fflush(stdout)强制刷新缓冲区。 换行是指直接到同一位置的下一行&#xff0c;回车是回到开头。

【MySQL数据库】 七

本文主要介绍了Java的JDBC编程的过程. 超详细 !!! 一.JDBC JDBC就是通过Java代码,来操作数据库 由于我们在实际开发中,绝大部分都是用代码来操作数据库的 , 因此一个成熟的数据库,都会提供一些API让程序员来使用. 常见的数据库比如mysql / oracl / sqlserver / SQLite 都会提…

Python类与对象:类的定义、实例化、方法、属性、构造函数

文章目录 类的定义类的实例化方法属性构造函数Python 类和对象是面向对象编程的基础。在 Python 中,几乎所有东西都是对象,拥有属性和方法。类是创建对象的蓝图或模板。让我们一步步来探索类的定义、实例化、方法、属性以及构造函数,并提供详细的代码示例。 类的定义 在 P…

SQL注入漏洞 其他注入

文章目录 宽字节注入案例 HTTP头部注入Cookie注入base64User-Agent注入Referer 注入 SQL注入读写文件条件1.是否拥有读写权限2.文件路径3.secure_file_priv 读取文件写入文件 SQLMap安装sqlmapkail 源安装仓库克隆 参数简介快速入门&#xff1b;SQLmap&#xff08;常规&#xf…

【Docker】设置容器系统字符集zh_CN.UTF-8退出失效:关于Docker容器配置环境变量,再次进入失效问题

设置容器系统字符集zh_CN.UTF-8退出失效&#xff1a;关于Docker容器配置环境变量&#xff0c;再次进入失效问题 修改正在运行的Docker容器内的字符集: 先进入Docker容器&#xff1a;docker exec -it 容器ID /bin/bash查看是否支持中文字符集&#xff1a;locale -a | grep zh&a…

B站双11,联手天猫暴涨2亿消费新势力

一直以来&#xff0c;手持高活跃、高粘性用户群体的B站是行业用来观察年轻人消费习惯的重要平台。以至于用户群体的不断壮大带动了B站的商业价值。如今B站的商业舞台越来越大&#xff0c;不断地向外界招手&#xff0c;欢迎更多品牌积极加入到这个千万年轻人聚集的内容社区。 2…

如何有效防爬虫?教你打造安全堡垒

企业拥抱数字化技术的过程中&#xff0c;网络犯罪分子的“战术”也更难以觉察&#xff0c;并且这些攻击越来越自动化和复杂&#xff0c;也更加难以觉察。在众多攻击手段总&#xff0c;网络爬虫是企业面临的主要安全挑战&#xff0c;对于企业所造成的经济损失是难以计量的。那么…