数据结构——链式二叉树的实现(详解)

news2024/12/23 11:02:23

呀哈喽。我是结衣。
不知道大家的递归学到怎么样呢?如果大家的递归功底不是很好,那么我相信在学完这篇文章后大家一定会对递归有一个更深层次的了解的。

构造链式二叉树

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

结构体的创建

typedef int BTdatatype;
typedef struct BinaryTreeNode
{
	BTdatatype data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}Treenode;

相信学了那么多数据结构的大家一定对此不会陌生,这里的结构体的创建的链表是相似的,毕竟这里的二叉树也是链式结构的。

函数的声明

Treenode* BTTreeCreat(BTdatatype x);
//前序
void PreOrder(Treenode* root);
//中序
void MidOrder(Treenode* root);
//求树的节点个数
int treesize(Treenode* root);
//求叶节点的个数
int TreeLeafSize(Treenode* root);
//求树的高度
int Treehight(Treenode* root);
//第k层的节点个数
int TreeLevelK(Treenode* root, int k);
//销毁
void treeDestroy(Treenode* root);
//查找
Treenode* BinaryTreeFind(Treenode*root,BTdatatype x);

没错学习二叉树就是为了让我们能够实现上面的功能。

构建二叉树

就想前文讲到的那样,现在我先面向结果构造一个如下图所示的二叉树出来,降低学习的成本,然后运用这棵树来实现二叉树的功能。
在这里插入图片描述

创造节点

Treenode* BTTreeCreat(BTdatatype x)
{
	Treenode* newnode = (Treenode*)malloc(sizeof(Treenode));
	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}

这个节点的创造和链表一模一样。不懂的小伙伴可以去看我实现链表的那篇文章。
节点创建完了,我们直接面向结果构造一个二叉树(如上图)

Treenode* root1 = BTTreeCreat(1);
	Treenode* root2 = BTTreeCreat(2);
	Treenode* root3 = BTTreeCreat(3);
	Treenode* root4 = BTTreeCreat(4);
	Treenode* root5 = BTTreeCreat(5);
	Treenode* root6 = BTTreeCreat(6);
	//Treenode* root7 = BTTreeCreat(7);
	root1->left = root2;
	root1->right = root4;
	root2->left = root3;
	root4->left = root5;
	root4->right = root6;
	//root5->right = root7;

构造完这个二叉树后我们就要来逐一的实现他的功能了。

函数的实现

我们要实现的功能有:打印二叉树的前序、中序,求二叉树的节点个数,求二叉树的叶节点个数,求二叉树的高度,求第k层的节点个数。

二叉树前序遍历

在实现这功能之前,我们先来了解一下上面是前序、中序、后序:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
    简洁一点就是
    前序的访问顺序为根 左 右
    中序的访问顺序为左 根 右
    后序的访问顺序为左 右 根
    了解完毕,看代码
void PreOrder(Treenode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
	{
		printf("%d ", root->data);
		PreOrder(root->left);
		PreOrder(root->right);
	}
}

发现是递归了吗?没错要实现链式二叉树的遍历就是要用递归的。我们来分析一下这个代码:
递归的结束条件 节点为空时就返回
在这里插入图片描述
当我们进入函数1时,肯定是直接进入else中然后打印1,往下走,将root的左子树传入同函数,第二次进函数2打印2,再将2的左子树传进函数,再进入函数3打印3。这里要注意了,3的左子树为空,所以我们进入函数4的if里面打印N,然后再返回函数3,返回函数3后由于函数3还没执行完就继续往下执行,将3的右子树传给函数3-1,为空打印N,然后返回函数3此时函数3执行完毕退出函数3到函数2。后面差不多就略了,我们来画图看看。
在这里插入图片描述
就是这么调用的。
下面看看打印效果
在这里插入图片描述
和我们分析的一样。

中序遍历

中序遍历只需要把打印放再中间就可以了。

void MidOrder(Treenode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
	{
		MidOrder(root->left);
		printf("%d ", root->data);
		MidOrder(root->right);
	}
}

在这里插入图片描述
这就是它的打印结果大家可以自己分析分析。

求树的节点个数

求节点的个数,我们肯定就要遍历二叉树,所以肯定要用到递归(下面的函数实现也都要用到递归)
首先我们要确定递归的返回条件,当节点为空时我们就返回,并且我要返回0.

int treesize(Treenode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	return treesize(root->left) + treesize(root->right) + 1;
}

这个代码的本质其实就是,如果该节点不为空就加1,为空就加0。因为在我们调用的每个函数里面只要不是空节点,那么它就会加1然后返回,假设我们已经进入3节点了,当我们继续执行函数的时候,它将3的左右子树再次进入函数,因为左右子树为0。返回0回到3节点再加1回到节点2然后同上。最后我们就能遍历二叉树求的节点的个数了。

在这里插入图片描述

求叶节点的个数

叶节点就是指:没有左右子树的节点。
实现前我们肯定要确定递归停止的条件。当节点为空时返回0。那么我们应该知道当当叶节点进入函数的时候叶要返回,这次返回1。然后就是递归调用。

int TreeLeafSize(Treenode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (!root->left && !root->right)
		return 1;
	else
		return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

在这里插入图片描述
打印结果也是没有问题的。

求树的高度

要想求树的高度,我们可以这要求:

int Treehight(Treenode* root)
{
	if (root == NULL)
		return 0;
	int lefthight = Treehight(root->left) + 1;
	int righthight = Treehight(root->right) + 1;
	return lefthight > righthight ? lefthight : righthight;

	/*int lefthight = Treehight(root->left);
	int righthight = Treehight(root->right);
	return lefthight > righthight ? lefthight+1: righthight+1;*/

	//return fmax()
}

求树的高度本质上就是找最长的一条线路,我们利用lefthight来保存左边最长的线路,用righthight来保存右边最长的线路,最后利用三目操作符的性质来返回最长的一条线路。
在这里插入图片描述
我们还可以再加一个节点7放在5节点的右边再验证一次。
在这里插入图片描述
事实上没有问题。

求第k层的节点个数

这个可以说是我们目前实现函数里面最难想到的一种了。首先我们要想怎么把他拆分成一个个的子问题。再把那个图取过来看看。
在这里插入图片描述
假设我们要求的是第3层节点的个数,是不是可以转换成2,3节点的第二层。3,5,6节点的第一层。有了这个思路来实现起来就会变得容易了。

int TreeLevelK(Treenode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	else
		return TreeLevelK(root->left, k - 1) + TreeLevelK(root->right, k - 1);

}

我们通过将k不断地减小,将问题的难度逐渐的压缩。最终得到一个不能再压缩的子问题,这就是递归的本质!
看看实现的效果。
我们来求第3层的节点个数(前文我加了一个节点进去,所以二叉树一共是有7个节点的)
在这里插入图片描述

在这里插入图片描述
没有问题,下面我就把我的原码放再下面了

代码

tree.h

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
typedef int BTdatatype;
typedef struct BinaryTreeNode
{
	BTdatatype data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}Treenode;

Treenode* BTTreeCreat(BTdatatype x);
//前序
void PreOrder(Treenode* root);
//中序
void MidOrder(Treenode* root);
//求树的节点个数
int treesize(Treenode* root);
//求叶节点的个数
int TreeLeafSize(Treenode* root);
//求树的高度
int Treehight(Treenode* root);
//第k层的节点个数
int TreeLevelK(Treenode* root, int k);
//销毁
void treeDestroy(Treenode* root);

tree.c

#define _CRT_SECURE_NO_WARNINGS
#include "tree.h"

Treenode* BTTreeCreat(BTdatatype x)
{
	Treenode* newnode = (Treenode*)malloc(sizeof(Treenode));
	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
void treeDestroy(Treenode* root)
{
	assert(root);
	
}
void PreOrder(Treenode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
	{
		printf("%d ", root->data);
		PreOrder(root->left);
		PreOrder(root->right);
	}
}
void MidOrder(Treenode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
	{
		MidOrder(root->left);
		printf("%d ", root->data);
		MidOrder(root->right);
	}
}

int treesize(Treenode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	return treesize(root->left) + treesize(root->right) + 1;
}

int TreeLeafSize(Treenode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (!root->left && !root->right)
		return 1;
	else
		return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int Treehight(Treenode* root)
{
	if (root == NULL)
		return 0;
	int lefthight = Treehight(root->left) + 1;
	int righthight = Treehight(root->right) + 1;
	return lefthight > righthight ? lefthight : righthight;

	/*int lefthight = Treehight(root->left);
	int righthight = Treehight(root->right);
	return lefthight > righthight ? lefthight+1: righthight+1;*/

	//return fmax()
}

int TreeLevelK(Treenode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	else
		return TreeLevelK(root->left, k - 1) + TreeLevelK(root->right, k - 1);

}

test.c

#include "tree.h"



int main()
{
	Treenode* root1 = BTTreeCreat(1);
	Treenode* root2 = BTTreeCreat(2);
	Treenode* root3 = BTTreeCreat(3);
	Treenode* root4 = BTTreeCreat(4);
	Treenode* root5 = BTTreeCreat(5);
	Treenode* root6 = BTTreeCreat(6);
	Treenode* root7 = BTTreeCreat(7);
	root1->left = root2;
	root1->right = root4;
	root2->left = root3;
	root4->left = root5;
	root4->right = root6;
	root5->right = root7;
	PreOrder(root1);
	printf("\n");
	MidOrder(root1);
	printf("\n");
	printf("treesize:%d", treesize(root1));
	printf("\n");
	printf("treeleafsize:%d\n", TreeLeafSize(root1));
	printf("treehight:%d\n", Treehight(root1));
	printf("treelevelk:%d\n", TreeLevelK(root1, 3));
	
	return 0;
}


在这里插入图片描述

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

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

相关文章

国标GB28181安防监控平台EasyCVR周界入侵AI算法检测方案

在城市管理和公共安全领域&#xff0c;安全视频监控的重要性日益凸显。AI视频智能分析平台基于深度学习和计算机视觉技术&#xff0c;利用AI入侵算法&#xff0c;能够实时、精准地监测周界入侵行为。 TSINGSEE青犀在视频监控及AI视频智能分析领域拥有深厚的技术积累和丰富的实…

2020年3月25日 Go生态洞察:Go、Go社区与疫情大流行

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2024年最受欢迎的项目管理工具盘点

十大项目管理系统包括&#xff1a;1.产品研发项目管理工具&#xff1a;PingCode&#xff1b;2.通用项目协作工具&#xff1a;Worktile&#xff1b;3.开源项目管理系统&#xff1a;Redmine&#xff1b;4.IT/敏捷项目管理系统&#xff1a;Jira&#xff1b;5.免费个人项目管理&…

java基础进阶-线程池

1、线程池 线程池就是一个可以复用线程的技术。 2、应用场景 用户每发起一个请求&#xff0c;后台就需要创建一个新线程来处理&#xff0c;下次新任务来了肯定又要创建新线程处理的&#xff0c;而创建新线程的开销是很大的&#xff0c;并且请求过多时&#xff0c;肯定会产生大…

2023年【起重机械指挥】考试题库及起重机械指挥考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【起重机械指挥】考试题库及起重机械指挥考试资料&#xff0c;包含起重机械指挥考试题库答案和解析及起重机械指挥考试资料练习。安全生产模拟考试一点通结合国家起重机械指挥考试最新大纲及起重机械指挥考试真…

OpenSSL 使用AES对文件加解密

AES&#xff08;Advanced Encryption Standard&#xff09;是一种对称加密算法&#xff0c;它是目前广泛使用的加密算法之一。AES算法是由美国国家标准与技术研究院&#xff08;NIST&#xff09;于2001年发布的&#xff0c;它取代了原先的DES&#xff08;Data Encryption Stand…

设计师福利!2024在线图标设计网站推荐,不容错过的宝藏!

在当今竞争激烈的商业环境中&#xff0c;公司或个人品牌的视觉识别元素已经成为区分你和竞争对手的关键因素之一。一个独特而引人注目的标志可以深深扎根于人们的心中&#xff0c;并在消费者心中建立一个强烈的品牌印象。如果你正在寻找合适的工具来创建或改进你的标志&#xf…

Nginx系列-正向代理和反向代理

Nginx系列-正向代理和反向代理 文章目录 Nginx系列-正向代理和反向代理1. 三个对象2. 两种场景代理2.1. 正向代理2.2. 反向代理 3. 两种场景的对比3.1 为什么叫做反向代理3.2 正向代理和反向代理的作用 1. 三个对象 客户端&#xff1a;发出请求到代理&#xff0c;并接收代理的…

2019年11月20日 Go生态洞察:Go开发者调查启动

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

1128. 等价多米诺骨牌对的数量

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/number-of-equivalent-domino-pa…

主机的具体权限规划:ACL的使用

目的&#xff1a;针对某一用户或某一组来设置特定权限需求&#xff0c;针对上&#xff0c;接着设置 ACL可以针对单一用户&#xff0c;文件&#xff0c;或者目录来进行rwx的权限设置&#xff0c;对于需要特殊权限的设置非常有帮助。 第一&#xff0c;查看文件系统是否支持&…

frida - 2.hook使用

frida hook 方法 下面是frida客户端命令行的参数帮助 Frida两种操作模式 1.attach 模式 将一个脚本注入到 Android 目标进程,即需要App处于启动状态, 这意味着只能从 当前时机往后hook。 frida -U -l myhook.js com.xxx.xxxx参数解释: -U 指定对USB设备操作 -l 指定…

08 木谷博客系统RBAC权限设计

这节内容说一下木谷博客系统的权限设计,采用现在主流的权限模型RBAC,对应关系如下: 以上5张表都在mugu_auth_server这个库中 该部分的服务单独定义在user-boot这个模块中。 将角色、权限对应关系加载到Redis 木谷博客系统在认证中心颁发令牌的时候是将用户的角色保存到令牌…

LabVIEW通过编程将图形类控件的X轴显示为时间戳

LabVIEW通过编程将图形类控件的X轴显示为时间戳 每个版本的LabVIEW中都有属性节点&#xff0c;可以以编程方式调整X轴和Y轴格式。对于不同版本的LabVIEW&#xff0c;这些属性节点无法在同一个位置找到。请参阅以下部分&#xff0c;了解特定版本LabVIEW的相关属性节点的位置。 …

静态住宅IP代理实际应用:它的强大用途你知道吗?

静态住宅IP代理与动态IP代理相比&#xff0c;提供了更稳定的网络身份&#xff0c;使得企业在进行数据采集、区域定位营销和市场研究时更为高效。同时&#xff0c;它也是提高在线隐私保护和避免封禁的有效工具。 通过详细分析&#xff0c;你将能全面了解静态住宅IP代理的应用&a…

C语言题目强化-DAY12

题型指引 一、选择题二、编程题 ★★写在前面★★ 本题库源自互联网&#xff0c;仅作为个人学习使用&#xff0c;记录C语言题目练习的过程&#xff0c;如果对你也有帮助&#xff0c;那就点个赞吧。 一、选择题 1、请阅读以下程序&#xff0c;其运行结果是&#xff08; &#x…

IDEA不支持Java8了怎么办?

IDEA不支持Java8了怎么办&#xff1f; 01 异常发生场景 当我准备创建一个springboot项目时&#xff0c;发现Java8没了 02 问题的产生及其原因 查阅了官方文档之后&#xff0c;确认了是Spring Boot 不再支持 Java 8&#xff0c;不是我的问题&#xff0c;这一天终于还是来了 0…

互联网架构演变过程梳理和架构思想的学习

文章目录 版权声明业务架构单体模式中台战略去中台化 数据架构单数据库架构主从读写分库分表高速缓存数据多样化分布式文件nosql搜索引擎架构特点 应用架构单机调优动静分离SOA微服务 部署架构单机部署⻆⾊划分应⽤集群多层代理异地访问云平台 架构思想总结 版权声明 本博客的…

数据结构与算法之美学习笔记:27 | 递归树:如何借助树来求解递归算法的时间复杂度?

目录 前言递归树与时间复杂度分析实战一&#xff1a;分析快速排序的时间复杂度实战二&#xff1a;分析斐波那契数列的时间复杂度实战三&#xff1a;分析全排列的时间复杂度内容小结 前言 本节课程思维导图&#xff1a; 今天&#xff0c;我们来讲这种数据结构的一种特殊应用&am…

springcloud进销存管理系统源码

开发说明&#xff1a; jdk1.8&#xff0c;mysql5.7&#xff0c;idea&#xff0c;vscode springcloud springboot mybatis vue elementui 功能介绍&#xff1a; 后台管理&#xff1a; 统计分析&#xff1a;查看产品&#xff0c;采购&#xff0c;销售数量&#xff1b;统计近…