【数据结构和算法初阶(C语言)】二叉树的链式结构--前、中、后序遍历实现详解,节点数目计算及oj题目详解---二叉树学习日记③

目录

​编辑

1.二叉树的链式存储

2.二叉树链式结构的实现

2.1树的创建 

2.2二叉树的再理解

3.链式结构二叉树的遍历

3.1前序遍历实现:

​编辑

3.2中序遍历实现

3.3后序遍历

 

​编辑 

4.链式二叉树节点数目的计算 

4.1 总节点个数的计算

错误代码1:

错误代码2:

最优解:递归子问题划分解 

4.2叶子节点个数的计算

4.3第k层节点个数的计算

5.二叉树查找节点值为x的节点

错误代码1:

正确代码:

6、二叉树的层序遍历

6.1利用层序遍历判断树是否为完全二叉树

7.二叉树的创建

8.二叉树的销毁 


1.二叉树的链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链。

2.二叉树链式结构的实现

2.1树的创建 

手动快速创建一棵简单的二叉树

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{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;node1->_right = node4;node2->_left = node3;node4->_left = node5;node4->_right = node6;return node1;
}

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

2.2二叉树的再理解

二叉树是: 1. 空树 2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

3.链式结构二叉树的遍历

二叉树的遍历 

二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次。

二叉树的遍历有:前序/中序/后序的递归结构遍历:

  • 1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。根--左子树-右子树
  • 2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。左子树--根--右子树
  • 3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。左子树---右子树--根

3.1前序遍历实现:


//前序遍历
void PrevOrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//不为空打印节点内容printf("%d ", root->val);//遍历每个节点的左子树PrevOrder(root->left);//遍历每个节点的右子树PrevOrder(root->right);
}

递归展开图为:

 

3.2中序遍历实现

//中序遍历
void Inrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//遍历每个节点的左子树Inrder(root->left);//不为空打印节点内容printf("%d ", root->val);//遍历每个节点的右子树Inrder(root->right);
}

递归展开图为:

3.3后序遍历

void EndOrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//遍历每个节点的左子树EndOrder(root->left);//遍历每个节点的右子树EndOrder(root->right);//不为空打印节点内容printf("%d ", root->val);}

 

 

4.链式二叉树节点数目的计算 

4.1 总节点个数的计算

简单思想:遍历一遍

错误代码1:

int TreeSize(BTNode* root)
{int size = 0;//记录树节点的个数if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}

错误原因:每次调用函数,都会重新定义一个size,且赋值为0,当函数调用结束,变量销毁,size并没有带回调用这个函数的函数,最后输出的size为:1 

错误代码2:

对上一个代码改进,将局部变量定义为全局变量:
局部的静态全局变量只会被执行一次

}//求二叉树的节点个数
int TreeSize(BTNode* root)
{static int size = 0;//记录树节点的个数if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}

 

执行结果没有问题,但是当我们第二次调用这个函数或者再次计算其他树的节点个数的时候:

看到了明显的错误。第二次调用没有进行初始化,不符合用户的预期,因为static修饰的变的生命周期是全局,但是作用域没有扩张,不能手动修改。

修改方式为将size定义为全局变量,然后手动每次调用前都初始化一下size:

int size = 0;//记录树节点的个数
int TreeSize(BTNode* root)
{if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}int ret = TreeSize(node1);
printf("%d ", ret);
size = 0;
int obj = TreeSize(node1);
printf("%d ", obj);

最优解:递归子问题划分解 

原理图解:

return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;

如果节点为空就返回0,如果不为空就返回左子树节点个数+右子树几点个数+自身

 

执行过程如图:红色代表向下执行,绿色代表返回:

 

4.2叶子节点个数的计算

思想:是空节点就返回0,是叶子节点就返回1,不是叶子节点就返回左子树的叶子节点数+右子树的叶子节点数。

叶子节点的特征:左子树与右子树为空

//叶子节点个数
int TreeLeaSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL)//叶子节点{return 1;}//不是空也不是叶子节点:return TreeLeaSize(root->left) + TreeLeaSize(root->right);}

 

4.3第k层节点个数的计算

 

当层数减少到1,就返回这层节点的个数,每个节点都返回1,如果要求的k层没有节点就返回空

//第K层节点的个数
int TreeLevel(BTNode* root, int k)
{assert(k > 0);//层数都是正的,防止用户传错if (root == NULL){return 0;//树不存在或者遍历到空节点}if (k == 1){return 1;//递归到要求的那一层了}return TreeLevel(root->left, k - 1) + TreeLevel(root->right, k - 1);}

 

5.二叉树查找节点值为x的节点

推荐使用前序遍历效率高,后序遍历要最后才找根,根就遍历来两次,效率低了一层。

思路:如果根是就返回,如果不是就到左右子树中去找。

错误代码1:

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL){return NULL;}if (root->val = x){return root;}TreeFind(root->left, x);TreeFind(root->right, x);

问题:到左右子树中去寻找过后,加入找到返回调用这个函数的函数中没有接收这个返回值的东西。

正确代码:

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL){return NULL;}if (root->val = x){return root;}BTNode* ret = NULL;ret = TreeFind(root->left, x);if (ret){return ret;}ret = TreeFind(root->right, x);if (ret){return ret;}return NULL;//左右都没有找到}

6、二叉树的层序遍历

使用队列的思想,将树的根节点先入队列,将后面所有节点带出。

void LevelOrder(BTNode* root)
{Queue q;QUeueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据printf("%d ", front->val);if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}//删除头部元素QueuepPop(&q);}QueueDestory(&q);
}

6.1利用层序遍历判断树是否为完全二叉树

层序遍历:非空节点连续就是完全二叉树,非空节点不连续,非空节点中间有节点就不是完全二叉树

void LevelOrder(BTNode* root)
{Queue q;QUeueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据printf("%d ", front->val);if (front == NULL){break;//遇到空节点,跳出这层循环}QueuePush(&q, front->left);QueuePush(&q, front->right);//删除头部元素QueuepPop(&q);}//已经遇到空节点,如果队列后面的节点还有非空就不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据QueuepPop(&q);if (front != NULL){QueueDestory(&q);return false;}}QueueDestory(&q);return false;
}

 

7.二叉树的创建

8.二叉树的销毁 

由于每个节点都是在堆上malloc出来的,所以用完就销毁,返还给操作系统。

void TreeDestory(BTNode* root)
{if (root == NULL){return;}TreeDestory(root->left);TreeDestory(root->right);free(root);//root == NULL;这里不用置空,这里的root只是实参的零时拷贝//可以通过传入二级指针的方式来指针也可以在主函数置空}

 

 9.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

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

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

相关文章

使用git下载github/gitee仓库部分或单个文件的方法

前言 有些时候在github或者gitee仓库中我们只需要下载整个项目中的我门需要的那一部分文件夹或文件就行了,不需要下载所有的项目。这样可以节省很多流量和时间 步骤 1.建立一个新的 git 本地仓库 这里我在D:\test中初始化 命令: git init2.在本地仓…

【Yolov7】踩坑记录

#windows上调用cuda去 使用GPU来进行预测 -需要下载cudnn,cuDNN 9.0.0 Downloads | NVIDIA Developer -环境变量要配置 比如 : C:\Program Files\NVIDIA\CUDNN\v9.0\bin\11.8 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.7\bin 也可…

学习笔记Day15:Shell脚本编程

Shell脚本编程 Linux系统环境 Linux系统的4个主要部分:内核、shell、文件系统和应用程序。 内核是操作系统的核心,决定系统性能和稳定性shell :一种应用程序,是用户和内核交互操作的接口,是套在内核外的壳&#xff…

Stable Diffusion 进阶教程 - 二次开发(制作您的文生图应用)

目录 1. 引言 2. 基于Rest API 开发 2.1 前置条件 2.2 代码实现 2.3 效果演示 2.4 常见错误 3. 总结 1. 引言 Stable Diffusion作为一种强大的文本到图像生成模型,已经在艺术、设计和创意领域引起了广泛的关注和应用。然而,对于许多开发者来说&#xff…

使用倒模耳机壳UV树脂胶液制作舞台监听耳返入耳式耳机壳推荐的材料和工艺流程?

推荐的材料和工艺流程如下: 材料: UV树脂胶液:选择适合倒模工艺的UV树脂胶液,要求具有高透明度、良好的流动性和固化性能。模具材料:建议使用硅胶模具,因为硅胶模具具有较高的耐用性和稳定性,…

Linux 安装 JDK、MySQL、Tomcat(图文并茂)

所需资料 下载 1.1 软件安装方式 在Linux系统中,安装软件的方式主要有四种,这四种安装方式的特点如下: 安装方式特点二进制发布包安装软件已经针对具体平台编译打包发布,只要解压,修改配置即可rpm安装软件已经按照re…

C#自定义控件 生成 与 加入到项目

C#自定义控件生成 在C#中,自定义控件通常是通过继承现有的控件类(如UserControl、Form等)并添加或修改其属性和方法来实现的。以下是一个简单的示例,演示如何创建一个自定义控件: 首先,创建一个新的Window…

sonar+gitlab提交阻断 增量扫描

通过本文,您将可以学习到 sonarqube、git\gitlab、shell、sonar-scanner、sonarlint 一、前言 sonarqube 是一款开源的静态代码扫描工具。 实际生产应用中,sonarqube 如何落地,需要考虑以下四个维度: 1、规则的来源 现在规则的…

每日一练:LeeCode-200、岛屿数量【DFS递归+BFS队列】

给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网格的四条边…

MyBatis:查询与连接池

一、查询 1、多表查询 尽量避免使用多表查询,尤其是对性能要求较高的项目。因为多表查询必然会导致性能变低。 例如:select *from ta运行需要10ms,select *from tb 运行也需要10s。但是,select *from ta left join tb on ta.xx…

python初级第一次作业

一、 dayint(input("enter today day")) fdayint(input("enter num of day since today")) c((fday%7)day)%7 if c0:print("sunday") elif c1:print("monday") elif c2:print("tuesday") elif c3:print("wendnsday&quo…

Jmeter脚本优化——CSV数据驱动文件

使用 CSV 数据文件设置实现参数化注册 1) 本地创建 csv 文件,并准备要使用的数据,这里要参数化的是注册的用户名和邮箱。所以在 csv 文件中输入多组用户名和邮箱。 2) 通过测试计划或者线程组的右键添加->配置元件->CSV…

多线程合并练习题,线程安全(售票任务引入)--学习JavaEE的day30

day30 练习(day29) 注意代码注释,里面涉及代码实现遇到问题及解决方案,由于理解方便没有单独出来 1.计算任务 1.计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出…

FT232RL/FT232RNL替代GP232RNL USB转UART桥接控制器芯片低成本方案

关注过小编的朋友都知道,之前小编有推荐过FT232RL的替代产品GP232RL,软硬件直接兼容,无需做修改。随着产品的更新迭代,后面也出来了升级版GP232RNL,低成本方案,可直接替代FT232RL/FT232RNL,参数…

【数据结构】线性表的定义与基本操作

🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:数据结构 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进…

阿里二面:谈谈ThreadLocal的内存泄漏问题?问麻了。。。。

引言 ThreadLocal在Java多线程编程中扮演着重要的角色,它提供了一种线程局部存储机制,允许每个线程拥有独立的变量副本,从而有效地避免了线程间的数据共享冲突。ThreadLocal的主要用途在于,当需要为每个线程维护一个独立的上下文…

linux之sed编辑器指令练习

目录 一、sed编辑器 二、sed使用案例 1.1 s命令(substitute替换) 一、sed编辑器 sed编辑器比交互式编辑器快的多,可以简化数据处理任务,sed编辑器并不会修改文件,只会将修改后的数据,输出。 二、sed使用案例 首先…

RK3568平台 iperf3测试网络性能

一.iperf3简介 iperf是一款开源的网络性能测试工具,主要用于测量TCP和UDP带宽性能。它可以在不同的操作系统上运行,包括Windows、Linux、macOS等。iperf具有简单易用、功能强大、高度可配置等特点,广泛应用于网络性能测试、网络故障诊断和网…

【编译tingsboard】出现gradle-maven-plugin:1.0.11:invoke (default)

出现的错误: [ERROR] Failed to execute goal org.thingsboard:gradle-maven-plugin:1.0.11:invoke (default) on project http: Execution default of goal org.thingsboard:gradle-maven-plugin:1.0.11:invoke failed: Plugin org.thingsboard:gradle-maven-plugi…

mysql - 缓存

缓存 InnoDB存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中,也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以…