数据结构之二叉树详解[1]

在前面我们介绍了堆和二叉树的基本概念后,本篇文章将带领大家深入学习链式二叉树。

1.预备知识

2.二叉树结点的创建

3.二叉树的遍历 

3.1前序遍历

3.2中序遍历

3.3 后序遍历

4.统计二叉树的结点个数

5.二叉树叶子结点的个数

 6.二叉树第k层的结点个数

 7.总结


1.预备知识

由于二叉树的性质,我们在实现二叉树及其相关功能时会经常使用到递归的操作。

所以我们首先介绍一下递归算法。

在百度百科中,对于递归算法的定义是:某个函数直接或者间接地调用自身,这样原问题的求解就转换为了许多性质相同但是规模更小的子问题。

很多同学或许对这句话无法理解,没有关系。

在这里我们仅仅将其当作一个工具,在编写代码时,我们只需搞清楚繁杂的递归调用中的一次调用即可,不需要去深究他调用的过程。在文章后续的代码实现中,大家可以深刻体会到这句话的意思。

2.二叉树结点的创建

 既然是链式二叉树,对学习过链表的大家就很简单了。直接上代码

typedef char BTDataType;typedef struct BTreeNode//每一个结点的结构
{BTreeNode* left;//指向左树BTreeNode* right; //指向右树BTDataType data;//结点的值
}BTNode;BTNode* BuyNode(BTDataType x)//为新结点开辟空间
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}

3.二叉树的遍历 

二叉树的遍历有四种:前序遍历、中序遍历、后序遍历、层序遍历。

今天我们在这里讲解前三种遍历,层序遍历请看下回分解。

3.1前序遍历

前序遍历就是先遍历根结点,在遍历左子树,最后遍历右子树。

理解了概念我们直接上代码。

void PreOrder(BTNode* root)//前序 根 左子树 右子树
{if (root == NULL){printf("NULL");return;}else {printf("%c", root->data);//打印根节点PreOrder(root->left);//递归遍历左子树PreOrder(root->right);//递归遍历右子树}
}

这里我们就使用到了递归。该如何理解呢。

我们只关注一次调用,既然遍历左树,

那么我们就让根去找,即 PreOrder(root->left)。

遍历右树,即PreOrder(root->right)。

中间是如何进行调用的细节我们不需要深究,我们只需要知道这个递归的写法。剩下的交给计算机。如果大家仍没理解,可以画个对照代码画一个草图。

 

3.2中序遍历

中序遍历:先遍历左子树,在找根,最后遍历右子树。

代码如下

void InOrder(BTNode* root)
{if (root == NULL){printf("NULL");return;}else {InOrder(root->left);//遍历左树printf("%c", root->data);//找根InOrder(root->right);//遍历右树}
}

这里的递归我们仍然按一样的方法进行理解,只看一层调用。大家可以看图理解。 

3.3 后序遍历

后序遍历:先遍历左子树,再遍历右子树,最后找根。

void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL");return;}else {PostOrder(root->left);//遍历左树PostOrder(root->right);//遍历右树printf("%c", root->data);//找根}
}

后序的图大家就自己画画吧。 

4.统计二叉树的结点个数

 如何统计二叉树的结点个数呢。

思路很简单,没遍历一个非空结点,个数加一。我们看下面代码。

void TreeSize(BTNode* root)
{if (root == NULL)return;int count = 0;count++;//既然不为空,那么一定就有一个结点的存在TreeSize(root->left);TreeSize(root->right);
}

如果大家觉得这个代码对的话,那就掉入陷阱了!

在递归调用中,count存在于栈帧之中,并且递归的每一次调用都会开辟新的一块栈帧,这样的话我们的count就无法进行值的保存而达到连续的效果。所以这里我们不能这样进行实现。

下面我们介绍正确的写法。

void TreeSize(BTNode* root,int*p)//p为指向节点个数的指针
{if (root == NULL)return;++(*p);//结点个数++,既然不为空,那么一定有一个根结点,所以先++TreeSize(root->left, p);//遍历左子树的结点TreeSize(root->right, p);//遍历右子树的结点
}

我们在这里运用定义了一个外部指针变量,用来记录结点的个数,传入的是记录结点个数变量的地址。这样就可以避免 原来出现的问题了。

 其实这里还有个更加简单的方法,直接用root来记录结点个数。

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

5.二叉树叶子结点的个数

 既然可以得到结点总数,那叶子结点是不是也可以得到呢。我们先上代码再解释。

int BTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL) //叶子结点的特点{return 1;}return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);//找叶子结点
}

既然要找叶子结点首先我们要知道叶子结点的性质:左右子树均为空。

所以我们在递归的时候需要加入叶子结点的判断条件,如果是叶子结点就要返回。

当然在进行递归前要判断根节点是否存在或者根结点是否有值,我们将其划分为代码中的一中情况。

 6.二叉树第k层的结点个数

我们该如何求任意一层的结点个数呢。

我们将求第k层的结点个数转化为求第k-1层结点的左右子树个树是不是就和我们求叶子结点个数大同小异了呢!

int BTreeLayerSize(BTNode* root,int k)
{if (root == NULL)return 0;if (k == 1)return 1;return BTreeLayerSize(root->left, k - 1) + BTreeLayerSize(root->right, k - 1);
}

我们加入需要的层数k,我们从根结点往下找第k层,如果要使其找到k层,就要使k=1。我们画图理解 。

我们假设要求第三层的结点个数,k=3。根结点为第一层,与其匹配的k=3,所以到找到第三层,就要使k--,简而言之,我们要找到的那一层匹配的k=1。这里可能有点绕,大家可以试着自己画图理解 。

 7.总结

 二叉树的内容到这里并没有结束,后序的内容需要用到队列的知识,所以我会在穿插队列的实现后继续为大家讲解二叉树的相关知识!敬请期待!

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

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

相关文章

自动驾驶占据感知的综述:信息融合视角

24年5月香港理工的论文“A Survey on Occupancy Perception for Autonomous Driving: The Information Fusion Perspective“。 3D 占据感知技术旨在观察和理解自动驾驶车辆的密集 3D 环境。该技术凭借其全面的感知能力,正在成为自动驾驶感知系统的发展趋势&#x…

最新极空间部署iCloudpd教程,实现自动同步iCloud照片到NAS硬盘

【iPhone福利】最新极空间部署iCloudpd教程,实现自动同步iCloud照片到NAS硬盘 哈喽小伙伴们好,我是Stark-C~ 我记得我前年的时候发过一篇群晖使用Docker部署iCloudpd容器来实现自动同步iCloud照片的教程,当时热度还很高,可见大家…

【C++】string|迭代器iterator|getline|find

目录 ​编辑 string 1.string与char* 的区别 2.string的使用 字符串遍历 利用迭代器遍历 范围for遍历 反向迭代器 字符串capacity 字符串插入操作 push_back函数 append函数 运算符 ​编辑 insert函数 substr函数 字符串查找函数 find函数 rfind函数 …

用面向对象的思想编写实时嵌入式C程序

实时嵌入式系统的软件一般由C语言编写,程序结构基本上都是这样的: // 主程序 int main(void) {init(); // 初始化while(1){tick(); // 业务逻辑}return 0; }// 计时器 static unsigned int g_timer_tick_cnt 0; // 时钟中断回调 void isr_time…

CAST: Cross-Attention in Space and Time for Video Action Recognition

标题:CAST: 时空交叉注意力网络用于视频动作识别 原文链接:2311.18825v1 (arxiv.org)https://arxiv.org/pdf/2311.18825v1 源码链接:GitHub - KHU-VLL/CASThttps://github.com/KHU-VLL/CAST 发表:NeurIPS-2023(CCF A…

vaspkit 画 Charge-Density Difference

(echo 314;echo $(cat 1))|vaspkit 文件1提前写好使用的CHGCAR路径 SPIN_DW.vasp ../ML2scf/SPIN_DW.vasp ../ML1scf/SPIN_DW.vasp POSite and negative 默认为blue,and 青色 (RGB 30 245 245) 正值:blue 。负值:青色 RGB 30 245 245。 提示&…

自动化测试基础 --- Jmeter

前置环境安装 首先我们需要知道如何下载Jmeter 这里贴上下载网站Apache JMeter - Download Apache JMeter 我们直接解压,然后在bin目录下找到jemter.bat即可启动使用 成功打开之后就是这个界面 每次打开可以用这种方式切换成简体中文 或者直接修改properties文件修改对应的语言…

JVM 双亲委派机制详解

文章目录 1. 双亲委派机制2. 证明3. 优势与劣势 1. 双亲委派机制 类加载器用来把类加载到 Java 虚拟机中。从JDK1.2版本开始,类的加载过程采用双亲委派机制,这种机制能更好地保证 Java 平台的安全。 1.定义 如果一个类加载器在接到加载类的请求时&…

电商核心技术揭秘56:客户关系管理与忠诚度提升

相关系列文章 电商技术揭秘相关系列文章合集(1) 电商技术揭秘相关系列文章合集(2) 电商技术揭秘相关系列文章合集(3) 文章目录 引言客户关系管理(CRM)的重要性提升顾客体验数据驱…

常见磁盘分区问题

给磁盘分区有几个主要的原因: 组织和管理数据:分区可以帮助用户更好地组织和管理数据。例如,你可以在一个分区上安装操作系统,而在另一个分区上存储个人文件。这样,即使操作系统崩溃或需要重新安装,你的个…

Milvus 简介与核心特性

一、Milvus 概述 Milvus 是一个开源的向量数据库,由 Zilliz 公司发起并维护。它专为处理非结构化数据而设计,能够存储、检索和分析大量的向量数据。Milvus 的名字来源于拉丁语,意为“一万”,象征着其处理大规模数据集的能力。 M…

Flink CDC 原理

简介 Flink CDC(Change Data Capture)是 Apache Flink 提供的一个变更数据捕获工具集。它可以监控数据库的变更,并将这些变更实时地以流的形式提供给下游系统,这些变更包括插入、更新和删除操作。 Flink CDC 适用于需要实时数据…