二叉树的链式结构和遍历(下)

又见面了,小伙伴们。今天我们继续来学习二叉树,今天的内容相对来说比较容易理解,前提是需要你们自己动手画图才会好理解。眼过千遍不如手过一遍。所以小伙伴们要多动手哦。直接开始今天的学习吧

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

1.1 前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;BTNode* BuyNode(BTDataType x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");return NULL;}node->data = x;node->left = NULL;node->right = NULL;return node;
}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);BTNode* node7 = BuyNode(7);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;node5->left = node7;return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
我们前面学过二叉树的概念是
1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
任何一个二叉树都可以看成有3个部分组成:根,左子树,右子树。每个子树又可以分成上述3个部分,一直分到它们的左右子树都为空时停止。
1.2 二叉树的遍历
1.2.1前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根, 所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树 NLR LNR LRN 分别又称为先根遍历、中根遍历和后根遍历。
下面来看一个二叉树,小伙伴们可以先试试它的前序,中序,后序都是什么。
第一次写这个的时候小伙伴们可能还搞不懂,就是直到左右子树都是空的时候才会停止,只要还能继续往下走,就要一直继续走。记住这句话应该就没有问题了。建议一定要自己画图才能理解其中的意思。
来看代码吧(如果自己想在电脑上试一下的话,要把开头给的代码补上才完整)
//前序
void PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}//中序
void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}//后序
void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}int main()
{BTNode* root = CreatBinaryTree();PrevOrder(root);printf("\n");InOrder(root);printf("\n");PostOrder(root);printf("\n");
}

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

总体的思想就是递归思想,我会画一个前序的递归图帮助小伙伴们更好的理解,其它的遍历小伙伴们可以自己试一下哦。

1.2.2 层序遍历
层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。(不能用递归来表示)
代码实现:
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->data);if (front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}printf("\n");QueueDestroy(&q);
}

如果想添加以前写的队列的代码的话,可以把队列文件的.c和.h 文件复制然后添加到二叉树文件的里面,如图所示

添加完之后需要对头文件做一些修改

2.求二叉树的各种节点问题

2.1计算节点个数

计算节点个数有2个方法:

方法1:把size定义成全局变量,然后遍历整棵数,如果不空的话,size++

int size = 0;
void BTreeSize(BTNode* root)
{if (root == NULL)return 0;++size;BTreeSize(root->left);BTreeSize(root->right);
}
BTreeSize(root);//方法1
printf("BTreeSize:%d\n", size);

方法2:分治法。左子树+右子树+1(1代指的是根)

举个例子,假如学校校长想要统计学生个数,那么是不是校长先给院长下达命令,然后院长在给辅导员下达命令,最后辅导员再给各班班长下达命令,让班长统计人数,然后依次上报,最后校长就知道学生有多少人了。递归思想就是这样

int BTreeSize(BTNode* root)
{/*if (root == NULL){return 0;}return BTreeSize(root->left) + BTreeSize(root->right) + 1;*/return root==NULL?0: BTreeSize(root->left) + BTreeSize(root->right) + 1; 
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.2 计算叶子节点个数

这个我们应该很好想到,就是当左子树和右子树为空的时候就是到叶子节点了。递归的终止条件有2个。当树为空时返回0,当左子树和右子树都为空的时候返回1

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);
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.3 计算二叉树高度

二叉树的高度由最长路径决定的,哪个路线最长,树的高度就是多少

先定义2个变量分别计算左右子树的长度,哪个树长树的高度就是它。

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

当然也可以这样写,不过这种写法的效率非常低,当数据量非常大的时候就会浪费很大的时间

int BTreeHight(BTNode* root)
{if (root == NULL)return 0;return BTreeHight(root->left) > BTreeHight(root->right) ? BTreeHight(root->left) + 1 : BTreeHight(root->right) + 1;
}
2.4 计算第K层节点个数

可以转换成左子树的第k-1层和右子树的第k-1层。递归的结束条件是k==1且节点不为空。

int BTreeLevelKSize(BTNode* root, int k)
{assert(k);if (root == NULL)return 0;if (k == 1)return 1;return BTreeLevelKSize(root->left, k - 1) + BTreeLevelKSize(root->right, k - 1);
}
	printf("BTreeLevelKSize:%d\n", BTreeLevelKSize(root,3));

2.5 查找值为x的节点

我先展示一下经典的错位写法,当然我开始也是这样想的

BTNode* BTreeFind(BTNode* root, BTDataType x)//错误写法,找到还要返回上一层
{if (root == NULL)return NULL;if (root->data == x)return root;BTreeFind(root->left, x);BTreeFind(root->right, x);}

错误的原因就是找到值的话不是直接退出递归,而是要返回上一层,一直到开头的地方。其实只要自己画一个递归展开图就知道是怎么回事了。讲递归的时候我们就知道是有去有回,不是直接结束

正确思路就是定义变量要记录找到的值,然后直接返回就行。

BTNode* BTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret1 = BTreeFind(root->left, x);if (ret1)return ret1;BTNode* ret2 = BTreeFind(root->right, x);if (ret2)return ret2;return NULL;
}
2.6 判断是否为完全二叉树

这时候就会有人想可不可以用节点个数来判断是不是完全二叉树,只要在范围之内就是完全二叉树,那么这种想法是错误的,这个想法只能用来判断是不是满二叉树。我们已经知道完全二叉树的特征是最后一层可以不满,但叶子节点必须是连续的,所以说用节点个数来判断是行不通的。

具体实现如下:我们可以通过层序遍历来判断,只要队列不为空时,就继续往下走

代码如下:

bool BTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//遇到空就跳出if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);}//检查后面的节点有没有非空//有非空,就不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;}

 好了,小伙伴们,今天的学习就到这里,下一节我们来练习一些二叉树有关的习题,关于二叉树的初级部分就学完了。高级部分要等到我们学完C++后才能更好的理解。感谢大家的阅读。

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

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

相关文章

如何成为顶尖程序员?

如何成为顶尖程序员? 程序员是一种特殊的职业,但为什么大多数程序员无法达到顶尖水平?本文探讨了几个可能的原因,包括缺乏热情和动力、基础和原理的不足、实践和经验的匮乏,以及思考和创新的欠缺。了解这些原因可以帮助…

基于SpringBoot+MyBatis+Vue的电商智慧仓储管理系统的设计与实现(源码+LW+部署+讲解)

前言 博主简介👨🏼‍⚕️:国内某一线互联网公司全栈工程师👨🏼‍💻,业余自媒体创作者💻,CSDN博客专家🏆,Java领域优质创作者📕&#x…

Redis中文乱码问题

最近排查问题,发现之前的开发将日志写在redis缓存中(不建议这样做),我在查看日志的时候发现没办法阅读,详细是这样的: 查阅资料后发现是进制问题,解决方法是启动客户端的时候将redis-cli改为red…

【go从入门到精通】if else 条件控制

作者简介: 高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C,go等语言开发经验,mysql,mongo,redis等数据库,设计模…

电脑安装双系统windows和ubuntu server

1.创建Ubuntu-server的启动盘 首先要从官网下载Ubuntu-server18.04的ISO文件,用rufs烧录到U盘。如下所示 2. 磁盘分区 在windows创建两个盘(linuxboot 和linuxroot),后面一个一个用于boot,一个用于root. 3.开机U盘启…

Flutter学习10 - Json解析与Model使用

对于网络请求返回的 Json 数据&#xff0c;一般会进行如下解析&#xff1a; 将 Json String 解析为 Map<String, dynamic>将 Json String 解析为 Dart Model 发起一个返回 Json String 的网络请求 import package:http/http.dart as http;void main() {_doGet(); }_do…

用好商用无人自助咖啡机,真正实现“AI智能”制饮!

随着科技的不断进步和智能化技术的广泛应用&#xff0c;商用无人自助咖啡机作为餐饮行业的新宠&#xff0c;正逐渐改变着我们的生活方式和消费体验。通过结合人工智能技术&#xff0c;这些无人自助咖啡机正在实现真正的“AI智能”制饮&#xff0c;为消费者带来全新的咖啡体验。…

Qt 项目使用visual studio 进行开发调试

https://marketplace.visualstudio.com/items?itemNameTheQtCompany.QtVisualStudioTools2015 https://devblogs.microsoft.com/cppblog/bring-your-existing-qt-projects-to-visual-studio/ 正常Qt开发中&#xff0c;使用Qt Creator 进行windows下MSVC编译器的调试是一件挺麻…

Medium 级别反射型 XSS 攻击演示(附链接)

环境准备 如何搭建 DVWA 靶场保姆级教程&#xff08;附链接&#xff09;https://eclecticism.blog.csdn.net/article/details/135834194?spm1001.2014.3001.5502 测试 打开靶场找到该漏洞页面 先右键检查输入框属性 跟 Low 级别是一样的&#xff0c;所以咱们直接输入带 HTM…

如何查看局域网内所有的ip和对应的mac地址

1、windows下查看 方法一、 按快捷键“winr”打开运行界面&#xff0c;输入“CMD”回车: 输入以下命令&#xff1a; for /L %i IN (1,1,254) DO ping -w 1 -n 1 192.168.0.%i 其中 192.168.0.%i 部分要使用要查询的网段&#xff0c;比如 192.168.1.%i 192.168.137.%i 172.16.2…

AI修复老照片的一些参数设置

很久没更新CSDN文章了&#xff0c;这次给粉丝带来老照片修复流程 1>用ps修图 图章工具 笔刷 画笔修复 2>高清放大 3>lineattile 重绘 4>上色 具体可参考我的B站视频。 下面是一些笔记。 best quality,masterpiece,photorealistic,8k,ultra high res,solo,ext…

概念解析 | 现象揭秘:经验模态分解的奥秘

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:经验模态分解(Empirical Mode Decomposition, EMD) 概念解析 | 现象揭秘:经验模态分解的奥秘 Decomposing Signal Using Empirical Mode Decomposition — Algorith…

Spring MVC入门(4)

请求 获取Cookie/Session 获取Cookie 传统方式: RequestMapping("/m11")public String method11(HttpServletRequest request, HttpServletResponse response) {//获取所有Cookie信息Cookie[] cookies request.getCookies();//打印Cookie信息StringBuilder build…

Spring+thymeleaf完成用户管理页面的增删查改功能

目录 知识点&#xff1a; 路由重定向 redirect:/*** 登录 控制层代码 接口 sql配置 页面效果 添加用户 控制层代码 接口 sql配置 页面效果 查看信息 控制层代码 接口 sql配置 页面效果 修改信息 控制层代码 接口 sql配置 页面效果 条件查询 控制层代码 …

Vue3:用重定向方式,解决No match found for location with path “/“问题

一、情景说明 在初学Vue3的项目中&#xff0c;我们配置了路由后&#xff0c;页面会告警 如下图&#xff1a; 具体含义就是&#xff0c;没有配置"/"路径对应的路由组件 二、解决 关键配置&#xff1a;redirect const router createRouter({history:createWebHis…

「渗透笔记」致远OA A8 status.jsp 信息泄露POC批量验证

前言部分 在本节中&#xff0c;我会分两部分来说明致远OA A8 status.jsp 信息泄露的验证问题&#xff0c;其实就是两种验证方式吧&#xff0c;都一样&#xff0c;都是批量验证&#xff0c;主要如下所示&#xff1a; 通过Python脚本进行批量验证&#xff0c;但是前提是你可以收…

vue3+threejs新手从零开发卡牌游戏(九):添加抽卡逻辑和动效

首先优化下之前的代码&#xff0c;把game/deck/p1.vue中修改卡组方法和渲染卡组文字方法提到公共方法中&#xff0c;此时utils/common.ts完整代码如下&#xff1a; import { nextTick } from vue; import * as THREE from three; import * as TWEEN from tweenjs/tween.js impo…

哈希表(c++)

1、介绍 哈希表&#xff0c;也称为散列表&#xff0c;是一种非常高效的数据结构。它通过将键&#xff08;Key&#xff09;映射到数组的特定位置来快速查找、插入和删除数据。这个映射过程由哈希函数&#xff08;Hash Function&#xff09;完成&#xff0c;该函数将键转化为一个…

【JS】深度学习JavaScript

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【JS】深度学习JavaScript &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一:JavaScript1.1 JavaScript是什么1.2 JS的引入方式1.3 JS变量1.4 数据类型1.5 …

三款优秀的伪原创改写软件,为你高效创作

近年来&#xff0c;随着互联网内容的爆炸式增长&#xff0c;原创内容的重要性日益凸显。然而&#xff0c;对于许多写手和创作者来说&#xff0c;时间和灵感可能成为限制因素。为了解决这个问题&#xff0c;伪原创改写软件应运而生。这些软件利用先进的算法和自然语言处理技术&a…