数据结构——小小二叉树第三幕(链式结构的小拓展,二叉树的创建,深入理解二叉树的遍历)超详细!!!

数据结构——小小二叉树第三幕(链式结构的小拓展,二叉树的创建,深入理解二叉树的遍历)超详细!!!
在这里插入图片描述

文章目录

前言

上篇博客我们学习了链式结构的二叉树,从递归角度实现了二叉树的前中后序遍历以及各种有关二叉树的结点个数和高度等函数,今天我们来学习一个不使用递归的二叉树的层序遍历以及一些二叉树有关的算法题,发车咯

一、二叉树的层序遍历

二叉树的层序遍历就是从所在二叉树的根结点出发首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
层序遍历还要借助我们的数据结构:队列

对队列不熟悉的可以看看我的这篇博客嗷
链接:栈和队列

有图才能有真相

在这里插入图片描述
其实就是当前一层的结点入队列然后一个一个出队列,出队列时把该节点的左右孩子入队列,这样下一个层级的结点就跟着上一层结点之后啦,重复这个操作,层序遍历就完成啦。话不多说,上代码

这是队列的结构以及一些函数的声明
这里把之前的 int 改成 BTNode 就好啦 队列里面的元素是二叉树的结点类型*

typedef BTNode* QDataType;// int 改成 BTNode*typedefstructQueueNode// 队列内每个元素用链式结构 一个一个结点连接{ QDataType data;structQueueNode* next;}QueueNode;typedefstructQueue// 队列 { QueueNode* phead;// 队头 QueueNode* ptail;// 队尾int size;}Queue;voidQueueInit(Queue* pq);// 队列初始化 voidQueuePush(Queue* pq, QDataType x);// 入队 voidQueuePop(Queue* pq);// 出队 boolQueueEmpty(Queue* pq);// 判空函数  QDataType QueueFront(Queue* pq);// 获取对头元素  QDataType QueueBack(Queue* pq);// 获取队尾元素intQueueSize(Queue* pq);// 队内有效元素voidQueueDestory(Queue* pq);// 销毁队列 voidQueuePrint(Queue* pq);// 打印 方便测试代码 

借助队列实现层序遍历

// 层序遍历voidLevelOrder(BTNode* root){//借助数据结构——队列 Queue q;QueueInit(&q);QueuePush(&q, root);// 初始第一层的根结点入队列 while(!QueueEmpty(&q)){//取队头,出队头 BTNode* top =QueueFront(&q);QueuePop(&q); cout<< top->data;//将队头左右孩子入队列(不为空)if(top->left){QueuePush(&q, top->left);}if(top->right){QueuePush(&q, top->right);}}QueueDestroy(&q);}
学习了层序遍历之后,我们可以用它来判断二叉树是否为完全二叉树因为完全二叉树是一层一层去填满每一个结点的,不能跳着填。
思路:正常层序遍历二叉树,当遇到空结点时,跳出循环,此时如果是完全二叉树,那它之后的结点除了空结点是没有有效结点的,如果有,那就不是完全二叉树
在这里插入图片描述
// 判断⼆叉树是否是完全⼆叉树boolBinaryTreeComplete(BTNode* root){ Queue q;QueueInit(&q);QueuePush(&q, root);while(!QueueEmpty(&q)){//取队头,出队头 BTNode* top =QueueFront(&q);QueuePop(&q);if(top ==NULL){break;}//把不为空的队头结点的左右孩子入队列QueuePush(&q, top->left);QueuePush(&q, top->right);}// 第一个循环之后,来到第二个循环,没有遇到有效元素就是完全二叉树//队列不一定为空,继续取队头出队头 while(!QueueEmpty(&q)){ BTNode* top =QueueFront(&q);QueuePop(&q);if(top !=NULL){QueueDestroy(&q);returnfalse;}}QueueDestroy(&q);returntrue;}

层序遍历就到这里啦


二、二叉树的有关习题

2.1 单值二叉树

习题链接:单值二叉树

题目

在这里插入图片描述

思路

思路:本题给定一个二叉树,判断所有的结点的值是否相同
简简单单的一个题,就是递归判断每一个左右孩子是否与根结点相同就好啦
对于认真了解二叉树的遍历的我们,洒洒水啦

代码

classSolution{public:boolisUnivalTree(TreeNode* root){if(root ==NULL)returntrue;if(root->left && root->val != root->left->val)returnfalse;if(root->right && root->val != root->right->val)returnfalse;returnisUnivalTree(root->left)&&isUnivalTree(root->right);}};

轻松拿下

在这里插入图片描述

2.2 相同的树

习题链接:相同的树

题目

在这里插入图片描述


在这里插入图片描述

思路

思路:本题和上一题有些类似,算是上一个题的拓展学习吧,需要判断是不是两颗相同的树
我们就同时递归两个树就好啦,一边递归一边判断结点的值是否相等
另外再处理一下不是相同的两颗树的判断就好啦

代码

classSolution{public:boolisSameTree(TreeNode* p, TreeNode* q){if(q ==NULL&& p ==NULL)// 如果两颗树的当前结点都为空 返回truereturntrue;if(q ==NULL|| p ==NULL)// 如果其中有一个不为空 证明两颗树的结点个数不同returnfalse;if(p->val != q->val)// 值不相同returnfalse;returnisSameTree(p->left,q->left)&&isSameTree(p->right,q->right);// 递归到每个根节点的左右子树 }};

拿下拿下

在这里插入图片描述

2.3 对称的树

习题链接:对称的树

题目

在这里插入图片描述

思路

思路:本题要判断一颗树是否为对称二叉树
一开始没什么头绪,但是想到对称,就从对称入手了,除了第一层的根节点,如果符合对称条件,左右子树是两颗左右孩子相反的树,这好像与判断两颗相同的树有些许差别,但处理一下就好啦,递归第一颗树的左节点和第二颗树的右结点匹配,右结点与左结点匹配。

代码

classSolution{public:boolcheck(TreeNode* p, TreeNode* q){if(p ==nullptr&& q ==nullptr){returntrue;}if(p ==nullptr|| q ==nullptr){returnfalse;}// 当前结点是否相等 以及 左右孩子反过来匹配判断 return p->val == q->val &&check(p->left,q->right)&&check(p->right,q->left);}boolisSymmetric(TreeNode* root){// 递归第一个根结点的左右子树 分成两棵树来看 returncheck(root->left, root->right);}};

也是稳稳的过啦

在这里插入图片描述

2.4 二叉树的遍历

习题链接:二叉树的遍历

题目

在这里插入图片描述

思路

思路:本题看起来就有难度了,就像上篇博客留下来的一个小问题,构建二叉树
本题根据读入的先序遍历,根据字符串创建二叉树,然后再进行中序遍历
这颗树建起来还不算麻烦,毕竟已经给了先序遍历,我们就构建链式结构,然后递归字符串的下标就好啦,走一遍先序遍历,然后把值赋给结点

代码

#include<functional>#include<iostream>usingnamespace std;typedefstructBinaryTreeNode// 正常创建二叉树链式结构 {char data;structBinaryTreeNode* left;structBinaryTreeNode*right;}BTNode; BTNode*BuyNode(char ch)// 获取新结点函数 { BTNode* node =(BTNode*)malloc(sizeof(BTNode)); node->data = ch; node->left = node->right =NULL;return node;} BTNode*createTree(string arr,int* pi)// 建树过程 {if(arr[*pi]=='#')// 这里传了一个pi 表示字符串的下标 {(*pi)++;// 如果当前字符是 # pi++ 返回 returnNULL;} BTNode* root =BuyNode(arr[*pi]);// 根据先序遍历 获取值为arr[pi]的结点 pi++(*pi)++; root->left =createTree(arr, pi); root->right =createTree(arr, pi);return root;// 类似于先序遍历的过程建树 }voidInOrder(BTNode* root)// 再更具中序遍历输出就好了{if(root ==NULL)return;InOrder(root->left); cout<<root->data<<' ';InOrder(root->right);}intmain(){ string arr; cin>>arr;int i =0; BTNode* root =createTree(arr,&i);InOrder(root);return0;}

拿下

在这里插入图片描述

2.5 二叉树的遍历

本题就没有习题链接啦,是实验课的习题

题目

在这里插入图片描述

思路

思路:本题和上一题不同的是,上一题是给定先序遍历,然后再建树输出中序遍历。
但本题是给定后序遍历和中序遍历再输出层序遍历。听起来有点不知道怎么处理了,只给定先序遍历建树还好,这里是给定后序和中序遍历。
**想到后序遍历(左右根)和中序遍历(左根右 )的具体过程,可以根据后序遍历找根节点,然后再根据中序遍历找左右子树,**如此反复这个过程就好啦‘

假设我们有以下后序遍历和中序遍历的结果:

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

确定根节点:
在后序遍历中,最后一个元素 1 是根节点。

分割中序遍历:
在中序遍历中找到根节点 1 的位置,它将中序遍历分为左子树 3 2 和右子树 5 4 6。

递归构建子树:
对于左子树 3 2 和右子树 5 4 6,分别在后序遍历中找到对应的部分(去掉已处理的根节点)。
左子树的后序遍历是 3 2 ,右子树的后序遍历是 5 6 4。

对左子树和右子树重复上述过程,直到构建完整棵树。’

在这里插入图片描述


左子树:

在这里插入图片描述


右子树:

在这里插入图片描述


就是不断找当前根节点的左右子树,把一个大问题化成很多重复相同的子问题。

代码

这里小编就用c++来实现这个题目了嗷

#include<bits/stdc++.h>usingnamespace std;structTNode// 二叉树的结点结构 数据域 左右指针域 {int data; TNode* left; TNode* right;};voidLevelOrder(TNode* root)// 层序遍历 { queue<TNode*> q;// 这里小编就直接使用 stl库的 queue 函数啦 q.push(root);while(!q.empty()){ TNode* top = q.front(); q.pop();if(top == root) cout << top->data;else cout <<' '<< top->data;// 这里的输出需要注意一下格式 因为题目的最后没有空格 if(top->left) q.push(top->left);if(top->right) q.push(top->right);// 这些步骤就和前面讲的层序遍历一样 }} TNode*BuildTree(int* inorder,int* postorder,int n)// 至关重要的环节 建树{// 根据后序遍历和中序遍历建树 if(n ==0)returnnullptr; TNode* node =new TNode; node->data = postorder[n -1];// 这里直接找到根节点 为后序遍历的最后一个数据  node->left = node->right =nullptr;int pos =0;for(pos; pos < n; pos++){if(inorder[pos]== postorder[n -1])// 然后再在中序遍历找到根结点 分出左右子树 break;// pos左边的就是左子树 pos 右边的就是右子树 } node->left =BuildTree(inorder, postorder, pos);// 当前根节点的左子树  node->right =BuildTree(inorder + pos +1, postorder + pos, n - pos -1);// 右子树 return node;// 这里中序遍历要在pos结点之后 因为pos是根结点 }// 后序遍历在pos开始 因为根节点在结尾 intmain()// 然后就是反复循环左右子树递归就好啦 就像前面举例一样 {int inorder[50], postorder[50];// 创建中序遍历和后序遍历的数组 int n; cin >> n;for(int i =0; i < n; i++){ cin >> postorder[i];}for(int i =0; i < n; i++){ cin >> inorder[i];} TNode* root =BuildTree(inorder, postorder, n);// 建树LevelOrder(root);// 层序遍历 return0;}

也是稳稳拿下了

在这里插入图片描述

2.6 二叉树的有关选择题

先来了解一些新的二叉树的性质:

对任何一棵二叉树, 如果度为 0 的叶结点个数为 n0 , 度为2 的分支结点个数为 n2 ,则有 n0 = n 2 + 1

假设一个二叉树有a 个度为2的节点, b 个度为1的节点, c 个叶节点,则这个二叉树的边数是2a+b
另一方面,由于共有a+b+c 个节点,所以边数等于a+b+c-1
结合上面两个公式:
2a+b = a+b+c-1 ,即: a = c-1

话不多说,上题

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
    A 不存在这样的二叉树
    B 200
    C 198
    D 199
由前面推导出来的 a = c-1 叶子结点个数有200个

2.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2

已知是完全二叉树则有 b = 1或0 2a+b+1 = 2n 若要符合要求 b只能=1 所以 a = n-1c = n;

3.一棵完全二叉树的结点数为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12

完全二叉树最后一层之前都是满的,所以可以根据2的次幂来求层数,前面在最开始讲到满二叉树时有结点个数的公式:结点个数 = 2^k-1 为层数
2的9次幂是512 所以这颗树的高度为10

4.一个具有767个结点的完全二叉树,其叶子结点个数为()
A 383
B 384
C 385
D 386

完全二叉树,还是回到 b只能取1或0 则2a+b+1 = 767 b只能取0 推出 a = 383 c=384;

来一些链式二叉树的遍历题

1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA

越来越觉得完全二叉树是个好东西
左子树 b d e h 右子树 c f h

2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为
A E
B F
C G
D H

找根节点 给了先序遍历 那不就是E嘛

3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。
A adbce
B decab
C debac
D abcde

和我们前面根据中序遍历和后序遍历建树的题相似 , 这不是手拿把掐吗

4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为
A FEDCBA
B CBAFED
C DEFCBA
D ABCDEF

这个题更有意思,中序遍历和后序遍历相同,那不是只有左子树

总结

简单的一些二叉树的学习就到这里啦,目前小编的石粒还没有涉及到更深层次的二叉树
回顾前面两篇文章,从二叉树的基本概念到顺序结构二叉树(堆)的实现,再到链式结构二叉树的实现,然后是二叉树的遍历方式,最后到习题部分的巩固与深入,对二叉树的遍历越来越深刻,特别是根据中序遍历和后序遍历建树再层序遍历输出,对递归又有了进一步理解,小编持续进步中~~~~
下一篇我们将开启新的数据结构——排序,小编持续更新中,不要走开~~~~
在这里插入图片描述

Read more

天马G前端的使用

天马G前端的使用

1 复古掌机的选择 最近搞了个手柄,正好有一个闲置的小米9,就想着看能不能装一个复古掌机出来。 其实市场上也有很多现成的复古掌机,目前主要是安卓和Linux两种。整体上看安卓的目前占优一点,因为除了大家都能玩的模拟器,安卓平台还能玩安卓的游戏。 项目Android 掌机Linux 掌机 (ArkOS / JELOS / Batocera)启动速度20~40 秒5 秒以内UI一致性❌ 多 app 无统一样式✅ 完整游戏平台风格PS2(AetherSX2)✅ 可玩(Snapdragon / Dimensity / Unisoc)❌ 官方 Linux 版 core 不成熟Switch(Yuzu)✅ 安卓有社区版 Yuzu❌ 完全无解PSP/NDS/GBA etc✅ 但调用 APK,界面割裂✅ 全集成 Core,UI统一云游戏 / Steam Link✅ 完全支持⚠

By Ne0inhk
Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 随着 Flutter 3.x 全面拥抱 Wasm(WebAssembly),Dart 团队推出了全新的 package:web 来取代老旧的 dart:html。 package:web 是基于最新的 JS Interop 机制构建的,它不仅性能更好,而且兼容 Wasm GC 标准。 虽然这个库通过名字看是为 “Web” 平台的,但对于 OpenHarmony 开发者来说,了解它有着特殊的意义: 1. 混合开发:鸿蒙原生支持 ArkWeb (WebView),在 Flutter 中通过 JS互操作与 Web 页面交互是常见需求。 2.

By Ne0inhk
【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk
数据结构之顺序表(C语言)

数据结构之顺序表(C语言)

1 线性表 线性表是n个具有相同特性的数据元素的有限序列,是一种在实际中广泛应用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等。 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。 2 顺序表的分类 2.1 顺序表与数组的区别 顺序表是线性表的一种,顺序表的特性是:逻辑结构连续,物理结构也是连续的。顺序表的最底层结构是数组,但顺序表在数组的基础上实现了对数组的封装及增删查改等接口。我们可以把数组看成路边小摊,而顺序表就是米其林餐厅,二者虽然结构类似,但发挥的功能却不一样,档次也就自然有所差别。 2.2 静态顺序表 静态顺序表也就表示顺序表存储空间是提前给定的,无法改变。即使用定长数组来进行数据的存储。如下: struct SeqList//用结构体来完成顺序表的操作 { int arr[100];//定长数组 int size;//顺序表中有效的数据个数 }; 如果使用静态顺序表就要面临两个严峻的问题: 一、

By Ne0inhk