数据结构——链式二叉树的实现与分治编程思维(c语言实现)

news2024/11/15 6:52:55

目录

前言:

1.前置说明

2.链式二叉树的遍历 

2.1 前序,中序及后续遍历

2.2 前序遍历实现

2.3 中序遍历实现 

2.4 后续遍历实现 

3.结点个数以及高度等 

3.1 结点个数

3.2 结点高度 

3.3 叶子结点的个数 


前言:

   在之前的学习中,我们初步学习了二叉树的概念和实现二叉树的顺序结构,最主要的是使用二叉树的顺序结构建堆,从而实现堆排序,这一章我们要学习的是二叉树的另一个结构——二叉树的链式结构,与顺序结构不同的是,顺序结构的底层是一个数组,链式结构是使用递归将多个结点链接起来组成的二叉树,讲到这里,递归还不是很熟悉的小伙伴需要回去复习递归的知识才能更好的理解链式二叉树的实现,话不多是,我们马上开始这一期的学习吧。

1.前置说明

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

   使用结构体来定义二叉树的结点,里面包含了它的数据和它的左子树,右子树,我们不知道将来会存什么类型的数据在结点中,所以使用typedef关键字来对它的数据类型改名,现在我们使用的是int类型,如果我们以后要使用char类型,只需要将第一行的int改成char就能实现了,如果我们不使用这个操作,将来要更改数据类型时,只能在各个函数中一个一个改,几十行几百行代码我们要改类型工作量还不是很大,如果是几万行几十万行其中的工作量有多大可想而知。

typedef int BTDateType;
typedef struct BinaryTreeNode
{
	BTDateType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
BTNode* creratNode()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->left = node2;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node4->right = node6;

	return node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解 

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是
1. 空树
2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2.链式二叉树的遍历 

2.1 前序,中序及后续遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。 

     由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

     也就是说,前序遍历的访问顺序依次为:根结点,左子树,右子树。相应的,中序遍历的顺序依次为:左子树,根节点,右子树后序遍历的顺序依次为:左子树,右子树,根节点

我们有一棵二叉树:

假设空结点为N:

前序遍历:1   2   3   N   N   N   4   5   N   N   6   N   N

中序遍历:N   3   N   2   N   1   N   5   N   4   N   6   N 

后序遍历:N   N   3   N   2   N   N   5  N   N   6    4   1

其中N就是我们在访问一些结点的左右子树时发现是空树时的标记

2.2 前序遍历实现

  我们在前面已经简单的实现了一棵树,现在就可以使用这棵树实现一些像遍历,计算结点个数这样的操作了。

   前序遍历的代码实现也比较简单,使用递归实现前序遍历,我们要先访问根结点,再去访问左子树和右子树,就是先打印根节点的数据,然后依次调用自己传左子树和右子树实现递归,如果遇到空树,我们就打印N,然后使用return马上走出这个函数栈帧,回到上一个函数栈帧,直到回到根结点为止。

void FrontOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	FrontOrder(root->left);
	FrontOrder(root->right);
}

图解:

2.3 中序遍历实现 

   中序遍历的实现与前序遍历的原理大致相同,在前序遍历原有代码的基础上改变顺序就可以实现了:

void MiddleOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	MiddleOrder(root->left);
	printf("%d ", root->data);
	MiddleOrder(root->right);
}

2.4 后续遍历实现 

void BackOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	BackOrder(root->left);
	BackOrder(root->right);
	printf("%d ", root->data);
}

前序遍历结果:1 2 3 4 5 6
中序遍历结果:3 2 1 5 4 6
后序遍历结果:3 2 5 6 4 1

程序运行结果:

3.结点个数以及高度等 

3.1 结点个数

   计算结点个数这里很容易掉入一个坑中,计算结点个数必然要使用递归,多次调用同一个函数,但是我们的每一次调用,都会建立一个独立的栈帧,而函数结束之后,函数栈帧也随之销毁,函数栈帧里的变量也不会存在,所以就不能使用局部变量来计算,我们自然而然就想到使用静态变量来计算,因为静态变量是存在于于堆区的,函数栈帧销毁不会将静态变量回收,所以有计算节点个数的函数:

size_t TreeSize(BTNode* root)
{
	static size_t size;
	if (root == NULL)
	{
		return 0;
	}
	else
		size++;

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}

这个函数能不能算出结点个数呢,我们当前是这样一棵树:

共六个结点,来看程序运行结果:

这样一看是不是很正确呢,但是如果我们调用两次:

    二叉树的结点个数又变成12了,到这我们也发现问题所在了,由于size是静态变量,在内存中只存在一份,在第二次调用时size的值就是6,而二叉树的结点是6个,就这样size的值变成了12,所以这样的方法是行不通的。所以我们使用另一种方法——分治。什么叫分治呢,假如某个学校的校长想要统计一下在校师生人数,他就会通知各个学院的院长让他们统计人数,而各个学院的院长通知各个班的班主任,班主任通知各个班的班长,将人数统计出来加上自己就是人数,这样是不是就方便了许多呢:

所以我们计算二叉树结点的函数应该是这样:

size_t TreeNodeCount(BTNode* root)
{
	return root == NULL ? 0 : TreeNodeCount(root->left) +
		TreeNodeCount(root->right) + 1;
}

将自己与左右子树的结点个数加起来就是总的结点个数:

3.2 结点高度 

   计算结点高度也是一个带着坑的问题,这样的问题也需要使用递归实现,我们的方法是:将左右子树的高度分别算出来,然后将大的那个加一就是整棵树的高度,但是我们在计算二叉树高度时,需要将它们的结果存起来,如果不存起来,我们每次要拿到这个结果都要递归一次,如果这棵树高一点的话,其中的计算量是非常恐怖的,所以我们要使用这种将结果存起来的方法:

size_t TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	size_t leftheigh = TreeHeight(root->left);
	size_t rightheigh = TreeHeight(root->right);

	return leftheigh > rightheigh ? leftheigh + 1 : rightheigh + 1;
}

我们当前树的高度是3,来看看程序运行结果吧:

3.3 叶子结点的个数 

  如何计算叶子结点的个数呢,我们发现只有叶子结点没有左右子树,所以只要一个结点的左右子树为空我们就认为它是叶子结点,发现叶子结点我们就返回1,如果当前结点不是叶子结点我们就去计算左右子树的叶子结点个数,需要注意的是,如果我们访问的是一棵空树,程序马上就会崩溃,所以要加个判断:如果是一棵空树就马上返回0结束这个栈帧,这样程序就不会崩溃了。

size_t TreeLelfSize(BTNode* root)
{
	//如果是空树,要马上返回零,否则走到下一行代码会崩溃
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return TreeLelfSize(root->left) + TreeLelfSize(root->right);
}

这是我们当前的二叉树:

可以看到叶子结点有3个,来看看程序运行结果吧:

 我将代码放在下面,感兴趣的话可以试试哦:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
static size_t size;
typedef int BTDateType;
typedef struct BinaryTreeNode
{
	BTDateType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDateType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
	}

	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}

size_t TreeLelfSize(BTNode* root)
{
	//如果是空树,要马上返回零,否则走到下一行代码会崩溃
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return TreeLelfSize(root->left) + TreeLelfSize(root->right);
}
void FrontOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	FrontOrder(root->left);
	FrontOrder(root->right);
}

BTNode* creratNode()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->left = node2;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node4->right = node6;

	return node1;
}

size_t TreeSize(BTNode* root)
{	
	if (root == NULL)
	{
		return 0;
	}
	else
		size++;

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}
size_t TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	size_t leftheigh = TreeHeight(root->left);
	size_t rightheigh = TreeHeight(root->right);

	return leftheigh > rightheigh ? leftheigh + 1 : rightheigh + 1;
}

void MiddleOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	MiddleOrder(root->left);
	printf("%d ", root->data);
	MiddleOrder(root->right);
}
size_t TreeNodeCount(BTNode* root)
{
	return root == NULL ? 0 : TreeNodeCount(root->left) +
		TreeNodeCount(root->right) + 1;
}
void BackOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	BackOrder(root->left);
	BackOrder(root->right);
	printf("%d ", root->data);
}
int main()
{
	BTNode* root = creratNode();
	printf("前序遍历二叉树:");
	FrontOrder(root);
	printf("\n");

	printf("中序遍历二叉树:");
	MiddleOrder(root);
	printf("\n");

	printf("后序遍历二叉树:");
	BackOrder(root);
	printf("\n");

	printf("二叉树结点的数量:");
	
	printf("%zd\n", TreeNodeCount(root));

	printf("二叉树的高度为:");
	printf("%zd\n", TreeHeight(root));

	printf("二叉树的叶子节点有%zd个", TreeLelfSize(root));
	return 0;
}

本章完。  

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

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

相关文章

从“云、边、端”的统一管理,为传统工厂数字化转型赋能的智慧地产开源了

智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。 AI是新形势下数…

这些软件测试面试题一定要会,自动化测试面试题(含答案)

1、你会封装自动化测试框架吗&#xff1f; 自动化框架主要的核心框架就是分层PO模式&#xff1a;分别为&#xff1a;基础封装层BasePage&#xff0c;PO页面对象层&#xff0c;TestCase测试用例层。然后再加上日志处理模块&#xff0c;ini配置文件读取模块&#xff0c;unittest…

多模态大模型技术详解(图像分块、特征对齐)

多模态 多模态发展图像预处理自适应图像切割弥补语义损失 视觉编码器视觉文本特征对齐线性映射或MLPCross AttentionPerceiver ResamplerQ-Former&#xff08;Querying Transformer&#xff09;模型结构表示学习 大语言模型 这篇文档主要讲解目前比较流行的缝合式的多模态大模型…

设计模式—装饰者模式

一、什么是装饰者模式 装饰者模式是一种结构型设计模式&#xff0c;它允许你动态地向对象添加新的行为而不影响其原有的行为。它在运行时给对象动态地添加一些额外的职责&#xff0c;通常是在原有的行为基础上&#xff0c;通过装饰器进行一些修饰&#xff0c;实现了更加灵活的代…

【软考】流水线

目录 一、指令控制方式1.1 说明1.2 顺序方式1.3 重叠方式1.4 流水方式 二、流水线的种类三、流水的相关处理3.1 说明3.2 RISC 中采用的流水技术3.2.1 超流水线(Super Pipe Line)技术3.2.2 超标量(Super Scalar)技术3.2.3 超长指令字(Very Long Instruction Word&#xff0c;VLI…

阿一网络安全之log4j2漏洞CVE-2021-44228复现

漏洞简介 Apache Log4j 2 是对 Log4j 的升级&#xff0c;它⽐其前身 Log4j 1.x 提供了显 着改进&#xff0c;并提供了 Logback 中可⽤的许多改进&#xff0c;同时修复了 Logback 架构中的⼀些固有问题。 2021 年 12 ⽉&#xff0c;在 Apache Log4j2 中发现了⼀个 0-day 漏洞。 …

倒计时7天!MoonBit 游戏挑战赛即将开启!

基于 Wasm4 框架的 MoonBit 游戏开发指南 MoonBit 即将面向全国举办“编程创新挑战赛”&#xff0c;并包含游戏赛道。本教程将介绍本次比赛中使用的框架 Wasm4&#xff0c;以及如何使用 MoonBit 在 Wasm4 框架中编写游戏。相关赛事详情见文末。 如果你曾访问过 mooncakes 或我们…

这本大模型书太香了!全方位解析LLM-Agent 第一本给程序员看的AI Agent图书!

AI Agent火爆到什么程度&#xff1f; OpenAI创始人奥特曼预测&#xff0c;未来各行各业&#xff0c;每一个人都可以拥有一个AI Agent&#xff1b;比尔盖茨在2023年层预言&#xff1a;AI Agent将彻底改变人机交互方式&#xff0c;并颠覆整个软件行业&#xff1b;吴恩达教授在AI…

从0-1开发一个Vue3前端系统页面-10.博客页面优化及子菜单设计

注意&#xff1a; 本项目已将前端源码同步上传至Gitee&#xff0c;项目已开源&#xff0c; 仅供参考&#xff0c;不涉及商用&#xff0c;不得用其牟利&#xff0c;著作权归本人所有。 本系列后期只会对重要部分代码进行注释&#xff0c;难点会同步更新至专栏 开发遇到的问题_不…

linux df -h时没有查到root盘,root文件夹带着锁或者叉号的解决办法

文章目录 一、前言二、来龙去脉1、2、给root文件赋予权限3 、这个时候df -h 查看就可以看到root文件了 总结 一、前言 当时装的双系统&#xff0c;自认为会学习很多linux相关课程&#xff0c;买了个1T的固态&#xff0c;ubuntu上分了很多&#xff0c;结果显而易见&#xff0c;…

UE5学习笔记16-游戏模式中的一些事件,如何改变网格体和摄像头的碰撞

一、OnPostLogIn&#xff1a;此事件在玩家成功登录游戏后被调用 二、HandleStartingNuwplayer&#xff1a;在OnPostLogIn事件后被调用&#xff0c;可以用来定义新进入的玩家会发生什么 三、Spawn Default PawnAtTransform&#xff1a;这个事件触发游戏中实际的Pawn生成 四、…

bash: /home/xxx/anaconda3/bin/conda: No such file or directory

一背景 最近把conda 移动后&#xff0c;出现了一堆bug&#xff0c;目前pip不能使用&#xff0c;在此记录一下解决方案。 二报错信息 bash: /home/xxx/anaconda3/envs/yolov10/bin/pip3 /home/xxx/.conda/envs/yolov10/bin/python: bad interpreter: No such file or directo…

Leetcode 100.101.110.199 二叉树相同/对称/平衡 C++实现

Leetcode 100. 相同的树 问题&#xff1a;给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 /*** Definition for a binary tree node.* struct T…

多媒体技术(1.1)之图像分辨率

「分辨率」这个概念还有「解析度」等说法&#xff0c;所以能从字面上看出来&#xff0c;它描述的其实就是图像包含多少细节、有多「清晰」。但具体到怎么用数字来描述一个图像有多少细节&#xff0c;就有很多个描述的角度&#xff0c;于是「分辨率」有很多种意思。 相机的分辨…

代码随想录算法训练营day29 | 贪心算法 | 134.加油站、135.分发糖果、860.柠檬水找零、406.根据身高重建队列

文章目录 134.加油站思路小结 135.分发糖果思路拓展——环形分糖小结 860.柠檬水找零思路 406.根据身高重建队列思路小结 今天是贪心算法专题第三天&#xff0c;直接上题目 134.加油站 建议&#xff1a;本题有点难度&#xff0c;不太好想&#xff0c;推荐大家熟悉一下方法二 …

STM32基础篇:定时器 × 输入捕获

通道的概念 如下图右半部分&#xff0c;为定时器的总体结构框图&#xff1a; 可以看出&#xff0c;在时基单元下方&#xff0c;有四个长条形的结构&#xff0c;我们将其称之为&#xff1a;通道1~通道4&#xff1b;每一个通道都会连接一个IO引脚&#xff08;对应左半部分IO引脚…

OZON什么产品好卖丨OZON婴儿用具产品

Top1 摇铃 Деревянная стойка тренажер Монтессори для мобилей и игрушек для новорожденных / развивающая дуга 商品id&#xff1a;1557614414 月销量&#xff1a;707 OZON婴儿用具…

MSSQL 手工注入(第一关)

简介 SQL注入是一种安全漏洞&#xff0c;通过它可以执行意外的SQL命令或访问数据库中的信息。MSSQL注入通常发生在应用程序将用户输入作为SQL查询的一部分执行时&#xff0c;而没有对输入进行适当的验证或清理。 以下是MSSQL手工注入的流程&#xff1a; 一、打开靶场选择第一关…

进阶岛 多模态模型部署微调实践

一、任务介绍 follow 教学文档和视频使用QLoRA进行微调模型&#xff0c;复现微调效果&#xff0c;并能成功讲出梗图.尝试使用LoRA&#xff0c;或调整xtuner的config&#xff0c;如LoRA rank&#xff0c;学习率。看模型Loss会如何变化&#xff0c;并记录调整后效果(选做&#x…

【Electron】桌面应用开发启动直接打开一个网址或者浏览器打开一个网址

【Electron】桌面应用开发启动时直接打开一个网址或者跳转浏览器打开一个网址 前一篇有写过 Electron 桌面应用开发快速入门到打包Windows应用程序 但是现在需要程序打开的时候直接打开一个链接&#xff0c;在程序的窗口打开或者直接跳转浏览器打开 一、启动时直接打开一个网…