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

目录

前言:

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;}elsesize++;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;}elsesize++;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.rhkb.cn/news/408022.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【图解秒杀系列】秒杀技术点——多级缓存、分层过滤

【图解秒杀系列】秒杀技术点——多级缓存、分层过滤 多级缓存本地缓存分布式缓存 分层过滤 多级缓存 多级缓存在秒杀系统中是非常重要的一个技术点&#xff0c;是应对秒杀场景瞬时高并发读请求的一种有效手段。通过在数据库前面加入多个缓存层&#xff0c;达到过滤掉大多数读请…

优惠券秒杀项目

一、添加优惠券的同时&#xff0c;将优惠券信息&#xff0c;以及用户列表放到redis中 Override Transactional public void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher new SeckillVoucher();seckillVou…

easyexcel--多sheet页导入导出

多sheet页导出 核心代码就是下图里面的&#xff0c;使用EasyExcel.writeSheet创建一个sheet,然后用excelWriter写入就行了&#xff0c;很简单 GetMapping("downloadMultiSheet")public void downloadMultiSheet(HttpServletResponse response) throws IOException {…

【Qt】输入类控件QDateTimeEdit

目录 输入类控件QDateTimeEdit 例子&#xff1a;实现日期计算器 输入类控件QDateTimeEdit QDate Edit作为日期的微调框 QTime Edit作为时间的微调框 QDateTimeEdit作为时间日期的微调框 下面主要讲解QDateTimeEdit&#xff1a; 核心属性 属性说明 dateTime 时间⽇期的值.…

【Hot100】LeetCode—101. 对称二叉树

目录 1- 思路借助队列 2- 实现⭐101. 对称二叉树——题解思路 3- ACM 实现 原题连接&#xff1a;101. 对称二叉树 1- 思路 借助队列 1- 创建队列&#xff1a;Queue<TreeNode> queue&#xff0c;初始化加入 root.left 和 root.right2- 判断逻辑&#xff1a;while(!queu…

软件开发者的首选:最佳Bug测试工具Top 10

本篇文章介绍了以下软件bug测试管理工具&#xff1a;PingCode、Worktile、Test360、禅道、码云Gitee、优云测试、Jira、GitHub、Axosoft、Bugzilla。 在开发过程中&#xff0c;Bug的管理往往是最让人头疼的问题之一。小问题积累起来不仅会拖延项目进度&#xff0c;还可能影响到…

如何优雅处理异步组件加载:Vue 3 的 Suspense 特性

在日常开发中&#xff0c;我们可能会遇到网络不佳或内容加载时间较长的情况。如果当前页面没有任何内容提示&#xff0c;用户的体验非常糟糕&#xff0c;可能会反复刷新以便加载成功。因此&#xff0c;我们需要给用户提供一个加载中的效果&#xff0c;告知用户“我在努力加载中…

基于单片机的人体健康监测系统的设计

本设计以STM32F103C8T6单片机作为主控&#xff0c;通过MAX30102采集心率、血氧值&#xff0c;通过MSP20血压采集模块检测血压值&#xff0c;通过MLX90614红外体温采集模块检测体温值。OLED屏可以显示以上检测的信息&#xff0c;并可以通过蓝牙模块将信息发送给手机APP。当检测值…

利用VirtualBox安装CentOS系统

博主这次用VirtualBox虚拟机安装CentOS系统。无论是大小型项目都是要发布到云主机上面&#xff0c;必然要用到Linux系统&#xff0c;有的人的本地电脑硬件配置不高&#xff0c;没有办法运行数据库集群&#xff0c;所以只能借助云主机。毕竟云主机也是Linux系统&#xff0c;大家…

大数据-92 Spark 集群 SparkRDD 原理 Standalone详解 ShuffleV1V2详解 RDD编程优化

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

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

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

Leetcode-day31-01背包问题

46. 携带研究材料 1.dp数组代表的是什么&#xff1f; 这里的dp数组是一个二维数组&#xff0c;dp[i][j]是从前i个物品中任选放入容量j内的最大价值。 2.递推公式。 不放物品i&#xff1a;由dp[i - 1][j]推出&#xff0c;即背包容量为j&#xff0c;里面不放物品i的最大价值&am…

uniapp检测手机是否打开定位权限Vue3-直接复制粘贴

安卓示例&#xff1a; 苹果示例&#xff1a; 代码实现&#xff08;vue3写法&#xff09;&#xff1a; const checkGPS ()>{console.log(开始监听GPS状态);let system uni.getSystemInfoSync(); // 获取系统信息if (system.platform android) { // 判断平台var context …

全国上市公司网络安全风险指数(2001-2023年)

数据来源&#xff1a;本数据参考耿勇老师等&#xff08;2024&#xff09;做法采集了2001-2023年的上市公司年报&#xff0c;所有年报均来自于深交所和上交所官方网站&#xff0c;通过对上市公司的年报进行精读&#xff0c;提取出包括网络安全、网络攻击等在内的39个关键词构成企…

牛客笔试小题

目录 牛客.小红取数 牛客.哈夫曼编码​编辑 牛客.字符编码(上一道题的资料) 牛客.最小的完全平方数 牛客.小红取数 01背包问题:在满足总和必须为k的倍数的情况下&#xff0c;选择最大的总和 1.状态表示: dp[i][j]:表示从前面i个数字中挑选&#xff0c;总和%k等于j时候,最大的…

微服务——远程调用

为什么需要远程调用&#xff1f; 在微服务架构中&#xff0c;每个服务都是独立部署和运行的&#xff0c;它们之间需要相互协作以完成复杂的业务逻辑。因此&#xff0c;远程调用成为微服务之间通信的主要方式。通过远程调用&#xff0c;一个服务可以请求另一个服务执行某些操作或…

数学建模国赛获奖技巧

一、团队分工合作的技巧&#xff08;三角形配合&#xff09; &#xff08;1&#xff09;队长要组织多沟通多交流&#xff1b; &#xff08;2&#xff09;建议定期开组会&#xff0c;互相讲授自己学习的东西&#xff0c;一人学习&#xff0c;三人收获。 二、AI辅助思路解析&am…

Eureka 原理与实践全攻略

一、Eureka 概述 Eureka 在微服务架构中具有举足轻重的地位。它作为服务注册与发现的核心组件&#xff0c;为分布式系统中的服务管理提供了关键支持。 Eureka 的主要功能包括服务注册、服务发现、服务健康监测和自我保护机制。服务注册功能使得服务提供者能够在启动时将自身的…

【Unity】移动端草海解决方案

草海是开放大世界渲染的必不可少的因素&#xff0c;Unity 原生的 Terrain 草海效率较低&#xff0c;而且无法与 RVT 结合起来&#xff0c;无法在移动端上实现。因此我们自己搓出来一套草海系统&#xff0c;使用 C# 多线程辅助运算&#xff0c;并能支持割草、烧草等进阶玩法。草…

系统编程 网络 http协议

http协议------应用层的协议 万维网&#xff1a;http解决万维网之间互联互通 计算机web端网络只能看到文字 1.如何在万维网中表示一个资源&#xff1f; url <协议>&#xff1a;//<主机>&#xff1a;<端口>/<路径> ------------------------------…