【数据结构入门】二叉树(2)

目录

1.二叉树遍历顺序

1.1 前序(先根)遍历

1.2 中序(中根)遍历

1.3 后序(后根)遍历

1.4 层序遍历

1.5 深度优先遍历&广度优先遍历

2.二叉树的遍历

2.1 前根遍历(递归)

 2.1.1 多路递归分析

2.1.2 OJ题目:前序遍历

2.1.3 OJ题目:单值二叉树

2.2 中根后根遍历(递归)

3. 二叉树的节点数量

4. 求二叉树叶子结点数量

5. 求二叉树深度

6.OJ:翻转二叉树

7.相同的树

8.另一个树的子树


1.二叉树遍历顺序

        对于任意一个链式二叉树而言,可以将其看成三部分:根结点、左子树、右子树

1.1 前序(先根)遍历

先遍历根节点,再遍历左节点,再遍历右节点。对于上图而言的遍历顺序为,A->B->D->E->C;

1.2 中序(中根)遍历

先遍历左节点,再遍历根结点,最后遍历右节点。对于上图而言的遍历顺序为,D->B->E->A->C;

1.3 后序(后根)遍历

先遍历左节点,再遍历右节点,最后遍历根结点。对于上图而言的遍历顺序为,D->E->B->C->A

1.4 层序遍历

顾名思义按照一层一层来遍历所有的节点,A->B->C->D->E   ,层序遍历可以用来判断是否是完全二叉树,因为如果将NULL节点打印出来的话,其实可以发现A->B->C->D->E NULL NULL NULL NULL NULL NULL

有效节点和空节点其实是分开的,这就可以判断是否是完全二叉树。

1.5 深度优先遍历&广度优先遍历

深度优先可以理解先朝着深处遍历,实在不能遍历,再进行回溯,例如前中后序遍历;

广度优先可以理解为以跟为中心点,一层一层往外扩张,层序遍历就是广度优先遍历。

2.二叉树的遍历

2.1 前根遍历(递归)

        想要遍历以上的二叉树,我们可以使用上面介绍的遍历方法,由于树的结构就是天然的递归结构,那么我们可以使用递归方法来遍历树。

        首先需要创建一个二叉树的结构体,该结构体需要左子树指针右子树指针以及当前节点存放的数据。

        对于先根遍历,对于每一个节点来说都是先打印根结点再打印左子树再打印右子树,所以函数进入之后,直接打印根结点,递归调用自己的左子树和右子树,如果当前节点为空,那么说明已经到了叶子结点,需要进行函数的回退。

typedef struct BinaryTreeNode
{dataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BinaryTreeNode;// 遍历
void PreOrder(BinaryTreeNode* root)
{if (root == NULL){return;}// 先根printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}// 创建二叉树节点
BinaryTreeNode* CreateNode(char x) 
{BinaryTreeNode* ret = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode));ret->data = x;ret->left = NULL;ret->right = NULL;return ret;
}// 求二叉树的大小int main() 
{BinaryTreeNode* A = CreateNode('A');BinaryTreeNode* B = CreateNode('B');BinaryTreeNode* C = CreateNode('C');BinaryTreeNode* D = CreateNode('D');BinaryTreeNode* E = CreateNode('E');A->left = B;A->right = C;B->left = D;B->right = E;PreOrder(A);
}

 2.1.1 多路递归分析

        我们简单地对上面这个先根遍历进行展开,首先节点A进入函数,打印A节点,调用A的左子树,将B作为新函数的root节点,打印B,将B的左子树作为新函数的根结点,打印D,将D的左子树作为根节点,判断为空那么函数就返回上一层调用函数的位置,继续执行将B的右子树传入函数,打印B的右子树E,将B的左子树作为函数的参数调用,打印D,将D的左子树作为参数调用函数,由于D的左孩子是NULL,此时函数直接返回到上一层,将D的右孩子作为参数进行调用,此时D的右孩子是NULL,继续返回上一层,发现上一层函数执行完毕,那么就继续调用根为B的节点的右孩子的递归,此时B的右孩子是E,打印E,将E的左右孩子作为参数进行递归调用,由于均是NULL,所以直接返回,最后B作为根节点的函数已经调用完毕,最后再调用以A为节点的函数,此时函数执行到了A节点的右孩子,最终打印C,以及C的左右孩子均为NULL,所以直接返回。

2.1.2 OJ题目:前序遍历

二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

输入:root = [1,null,2,3]

输出:[1,2,3]

解释:

        这道题目本身的逻辑不难,需要思考的是,如何将本该输出的操作转换为存入数组,首先需要传入一个数组,一个下标的指针,这里传入指针是因为,下标始终贯穿整个递归结构,不能被销毁,需要一直存在;

        当将i下标的位置的元素赋值以后,需要立刻将下标++,再进行递归调用左子树和右子树。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/
/*** Note: The returned array must be malloced, assume caller calls free().*/typedef struct TreeNode TreeNode;int treeSize(TreeNode* root){if(root == NULL){return 0;}else{return 1 + treeSize(root->left) + treeSize(root->right);}}void preOrder(TreeNode* root,int* arr, int* i)
{if(root == NULL){return;}// 放入数组arr[(*i)] = root->val; ++(*i);preOrder(root->left,arr,i);preOrder(root->right,arr,i);}int* preorderTraversal(struct TreeNode* root, int* returnSize) {int* ret = (int*) malloc(sizeof(int)*treeSize(root));int i = 0;preOrder(root,ret,&i);*returnSize = treeSize(root);return ret;
}

2.1.3 OJ题目:单值二叉树

果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

只有给定的树是单值二叉树时,才返回 true;否则返回 false

示例 1:

输入:[1,1,1,1,1,null,1]
输出:true

        先考虑当前树,在考虑左右子树

        首先判断单个节点为空,那么说明为真,因为空节点其实不影响判断单值。

        若根结点不为空,在左节点不为空的情况下,如果左节点的值和根结点的值不等,说明不是单值,返回false;

        右节点同理;

        最后判断左子树和右子树如果同时是true,说明左右子树都是单值,若有一个为false说明就不是单值。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/typedef struct TreeNode TreeNode;
bool isUnivalTree(struct TreeNode* root) {// 只有一个节点是单值二叉树if(root == NULL){return true;}// 当左节点和根结点不相等返回假if(root->left != NULL && root->val != root->left->val){return false;}// 当右节点和根结点不相等返回假if(root->right!= NULL && root->val != root->right->val){return false;}// 左子树和右子树return isUnivalTree(root->left) && isUnivalTree(root->right);}

2.2 中根后根遍历(递归)

        如果理解了前序遍历,那么中根遍历其实就是先访问左子树再打印根,访问右子树。

// 中序遍历
void InOrder(BinaryTreeNode* root)
{if (root == NULL){return;}PreOrder(root->left);printf("%c ", root->data);PreOrder(root->right);
}// 后序遍历
void PostOrder(BinaryTreeNode* root)
{if (root == NULL){return;}PreOrder(root->left);PreOrder(root->right);printf("%c ", root->data);
}

3. 二叉树的节点数量

        同样可以使用递归来求,如果当前节点是NULL,那么就直接结束该函数,不然就将size++,这里需要注意的是由于递归调用函数是栈,在函数内定义size,每次函数结束的时候size会销毁,所以这里需要把size设置成静态局部变量。

        ++完size之后需要遍历左子树和右子树,这里遍历的顺序没有要求。

// 求二叉树的大小
int TreeSize(BinaryTreeNode* root)
{static int size = 0;if (root == NULL){return 0;}++size;// 遍历左右子树TreeSize(root->left);TreeSize(root->right);return size;
}

        这里有一个致命的问题,如果这个函数被调用多次,那么次数是会被累加计算的,因为函数执行结束的时候该变量是存储在静态区中,是不会被销毁的。

        所以这里的size是由外界传入的,如果此节点非空那么size++,这样也会解决上述提到的问题。

// 求二叉树的大小
void  TreeSize(BinaryTreeNode* root, int* size)
{if (root == NULL){return;}++(*size);// 遍历左右子树TreeSize(root->left, size);TreeSize(root->right, size);
}int main()
{BinaryTreeNode* A = CreateNode('A');BinaryTreeNode* B = CreateNode('B');BinaryTreeNode* C = CreateNode('C');BinaryTreeNode* D = CreateNode('D');BinaryTreeNode* E = CreateNode('E');A->left = B;A->right = C;B->left = D;B->right = E;//PreOrder(A);int size = 0;TreeSize(A, &size);printf("%d ", size);
}

        但是这种方法需要传入一个size,对于调用方法的人来说非常不友好,那么有没有一种方法,可以直接返回当前的根结点的二叉树节点数呢?

        若节点为空,返回0,若不为空就返回1(本身)+左子树+右子树的节点数量。

// 求二叉树的大小
int  TreeSize(BinaryTreeNode* root)
{if (root == NULL){return 0;}else{return 1 + TreeSize(root->left) + TreeSize(root->right);}
}

4. 求二叉树叶子结点数量

        首先使用递归来解决,判断当前节点是否为空节点,然后判断当前节点是否为叶子结点(左右子树为空),最后就是普通的节点,此时计算该叶子结点是左子树叶子结点+右子树叶子结点。


// 求二叉树叶子结点个数
int LeafSize(BinaryTreeNode* root) 
{// 是空吗if (root == NULL) {return 0;}// 是叶子节点吗if (root->right == NULL && root->left == NULL) {return 1;}return LeafSize(root->left) + LeafSize(root->right);
}

5. 求二叉树深度

        如果空节点,深度为0;除此以外,剩下的节点都按照左子树深度和右子树深度中最大的那一个再+1(本节点)计算。

// 二叉树的深度
int TreeDepth(BinaryTreeNode* root) 
{// 如果空节点深度就是0if (root == NULL) {return 0;}int leftDepth = maxDepth(root->left);int rightDepth = maxDepth(root->right);return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

6.OJ:翻转二叉树

给定一棵二叉树的根节点 root,请左右翻转这棵二叉树,并返回其根节点。

示例 1:

输入:root = [5,7,9,8,3,2,4]
输出:[5,9,7,4,2,3,8]

        如果根结点为空,直接返回NULL,如果不为空就交换根结点的左右节点,应用在左子树和右子树上,最后返回根结点。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/
struct TreeNode* flipTree(struct TreeNode* root) {if (root == NULL) {return NULL;}struct TreeNode* tmp = root->left;root->left = root->right;root->right = tmp;flipTree(root->left);flipTree(root->right);return root;
}

        还有一种解法:刚刚的解法是先处理根结点在处理左子树和右子树,属于前序遍历,那么可以先处理左右子树,在处理根结点。        

例如先将,2和7的子树进行翻转,最后将4的right连到2,4的left连接到7;

struct TreeNode* flipTree(struct TreeNode* root) {if (root == NULL) {return NULL;} else {struct TreeNode* tmp = root->right;root->right = flipTree(root->left);root->left = flipTree(tmp);return root;}
}

7.相同的树

给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:

输入:p = [1,2,3], q = [1,2,3]
输出:true

首先如果两个节点都是空,那么说明完全是相同的,直接返回true;

若一个节点为空另一个节点不为空,说明两个节点结构不同,返回false;

若两个节点都不为空,并且值还不相等,说明这两个节点不相等,返回false;

最后判断左右子树为true的情况下返回true,否则false;

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {// 都是空也相等if (p == NULL && q == NULL) {return true;}// 结构不同if (p == NULL && q != NULL) {return false;}if (q == NULL && p != NULL) {return false;}// qp都不为空,值不同if (p->val != q->val) { //此时如果两个值相同依旧不能判断结果,因为没有判断左右子树,所以这里判断不相等的时候return false;} // 结构相同、值相同,判断左右子树return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

8.另一个树的子树

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例 1:

输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true

示例 2:

输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false

从示例2可以得出,子树的定义是某一个子树到叶子结点与提供的子树完全相同,子树不能有子树。

        首先需要判断主树为空,那么就不需要判断子树了,没有树是NULL的子树,返回false;

然后判断若两棵树如果是同一棵树(借助上一题的接口),那么就返回true;

如果没有找到,那么就去递归左子树,若左子树没找到,再递归右子树。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/bool isSameTree(struct TreeNode* p, struct TreeNode* q) {// 都是空也相等if (p == NULL && q == NULL) {return true;}// 结构不同if (p == NULL && q != NULL) {return false;}if (q == NULL && p != NULL) {return false;}// qp都不为空,值不同if (p->val != q->val) { //此时如果两个值相同依旧不能判断结果,因为没有判断左右子树,所以这里判断不相等的时候return false;} // 结构相同、值相同,判断左右子树return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {// 如果主树是空的,就不需要比了if(root == NULL){return false;}// 如果主树和子树比对上了,返回trueif(isSameTree(root,subRoot)){return true;}// 如果没找到,先去左子树找,左子树没找到去右子树return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}

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

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

相关文章

【电机参数】电压、电流、转速标幺化推算过程

【电机参数】电压、电流、转速标幺化推算过程 文章目录[TOC](文章目录)前言一、标幺化目的——优化计算二、Q15与标幺化的关系三、标幺值计算1.电压标幺值2.电流标幺值3.转速标幺值四、参考资料总结前言 一、标幺化目的——优化计算 不同物理量的量纲和数值范围差异巨大&#…

v-scale-scree: 根据屏幕尺寸缩放内容

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…

linux设备驱动之字符设备驱动

一、cdev结构体‌成员/功能‌‌说明‌‌相关操作函数/宏‌‌kobj‌内嵌的kobject对象,用于Linux设备模型管理,实现引用计数和sysfs接口kobject_init()‌owner‌指向拥有该结构体的模块指针(通常为THIS_MODULE),防止模块…

★CentOS:MySQL数据备份

一、cp 命令备份特点:优点:备份恢复数据快:直接复制文件,无需进行数据转换和复杂的处理,因此备份恢复速度非常快缺点:需要停止数据库服务,灵活性差,占用空间大,可移植性差…

Python代码规范与静态检查(ruff/black/mypy + pyproject.toml + Makefile)自动化工具链介绍

文章目录**1. 核心工具的作用****(1) black:代码格式化工具****(2) ruff:代码质量检查工具****(3) mypy:静态类型检查工具****2. pyproject.toml:统一配置中心****示例配置**(pyproject.toml):*…

软件需求管理过程详解

需求管理过程需求管理是软件工程和系统开发中的核心过程,它确保项目始终围绕正确、稳定且可追溯的需求进行。在复杂系统开发中,需求往往动态变化,需求管理通过系统化的方法控制变更、维护版本、建立追溯关系,从而降低项目风险、保…

MySQL性能优化实战指南:从入门到精通的完整优化体系

MySQL性能优化实战指南:从入门到精通的完整优化体系🚀 前言:在当今数据驱动的时代,MySQL作为世界上最流行的开源关系型数据库,其性能优化能力直接决定了应用系统的响应速度和用户体验。本文将从多个维度深入探讨MySQL优…

KingbaseES主备读写分离集群安装教程

首先我们先要找数据库集群安装软件和脚本。这里我事先安装一台单机。 [rootlocalhost zip]# mkdir -p /home/kingbase/software [rootlocalhost zip]# scp -r * /home/kingbase/software/ #安装软件和脚本在单机版本的/opt/Kingbase/ES/V9/ClientTools/guitools/DeployTools/z…

electron程序适配loongArch64

一、原始项目 1.原始程序适配arm,x86国产linux设备;新增需求适配loongArch64麒麟v10sp1。 2.原始devDependencies "devDependencies": {"electron": "^17.2.0","electron-builder": "^23.0.3",}二、可能遇到的问…

窗口系统(windowing system)的架构思考

我想做一个通用窗口系统,窗口、控件等,一切都抽象成树形结构的层叠矩形块,可支持半透明、模糊等混合选项,那么每个窗口是不是需要一块存储区?我之前的代码为了计算模糊,还不止一块,要三块。那么…

极简工具箱:安卓工具箱合集

软件介绍 极简工具箱是一个安卓工具箱合集软件;软件支持安卓。 它支持将近 400 个实用功能,支持将近 40 款单机游戏,提供 140 多个实用网站导航,包括电子书导航、学习导航、设计导航、产品经理导航、大数据导航、文档格式转换、…

TOGAF八步一法笔记2

业务需求和验收标准一旦方向确定,接下来的关键就是:创建业务需求、明确验收标准当“预备阶段”完成,能力愿景和范围被管理层确认后,我们正式进入能力建设的“实施轨道”。而这个轨道的起点,是两个核心动作:…

各种读取csv文件的工具性能比较

在翻阅calamine作者的quick-csv存储库时无意中看到有个10年前的csv读取比赛, 把比赛选手源程序下载下来测试看到底有多快。 git clone https://bitbucket.org/ewanhiggs/csv-game.git这些源程序只有比赛程序本身,依赖的文件有的在主页,有的在makefile中…

HTML <iframe> 标签 如何把html写入iframe标签

标签 如何把html写入iframe标签 使用srcdoc属性 HTML iframe 标签 参考 定义和用法 <iframe> 标签定义行内框架&#xff08;内联框架&#xff09;。 行内框架用于在当前 HTML 文档中嵌入另一个文档。

Java Spark例子程序

目录spark基础&rdddocsRDDspark架构Spark 对比 hadoop MapReducespark maven依赖Spark的checkpointtransformations、shuffle、actionsreduceByKey的用法groupByKey的用法count / count distinct例子&#xff1a;单词计数例子&#xff1a;一批人员年龄数据求平均(rdd)例子&…

《代码重生:杨蓉与62.webp》

《代码重生&#xff1a;杨蓉与62.webp》2045年&#xff0c;星耀城。雨丝斜织在量子玻璃幕墙上&#xff0c;霓虹倒影如液态代码流淌。杨蓉坐在“时光回溯实验室”的终端前&#xff0c;面前悬浮着一行行泛黄的日志——那是从2018年GitHub快照中提取的原始构建记录。她指尖轻点&am…

软考 系统架构设计师系列知识点之杂项集萃(123)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(122) 第227题 某公司欲开发一种工业机器人,用来进行汽车零件的装配。公司的架构师经过分析与讨论,给出了该机器人控制软件的两种候选架构方案:闭环控制和分层结构。以下对于这两者候选框架的选择路由,错误的是(…

Sonatype Nexus Repository Manager docker版本安装

docker 网址 https://hub.docker.com/r/sonatype/nexus3 拉取镜像 docker pull sonatype/nexus3创建docker docker run -d -p 8081:8081 --name nexus --restart always sonatype/nexus3查看密码 docker exec nexus cat /nexus-data/admin.password导出docker image 镜像 …

Java Stream API:让业务数据处理更优雅

在 Java 业务开发中&#xff0c;我们经常需要对集合数据进行**筛选&#xff08;filter&#xff09;、转换&#xff08;map&#xff09;、聚合&#xff08;collect&#xff09;**等操作。比如从一批结果中过滤出符合条件的记录&#xff0c;就像这样&#xff1a; 假数据&#xf…

Win11和Win10共享打印机提示709用添加Windows凭据来解决的小方法

我们在使用共享打印机打印文件时或者添加共享打印机的时候&#xff0c;遇到了系统提示错误709的问题&#xff0c;导致打印失败、共享失败&#xff0c;如果你现在正好也遇到了这一问题&#xff0c;那么不妨来看看下面吴师傅使用过的这个方法&#xff0c;希望可以能够帮助大家有效…