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

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

文章目录

前言

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

一、二叉树的层序遍历

二叉树的层序遍历就是从所在二叉树的根结点出发首先访问第一层的树根结点,然后从左到右访问第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

Clawdbot部署Qwen3:32B实操:解决‘gateway token missing’的三种Token注入方式对比

Clawdbot部署Qwen3:32B实操:解决‘gateway token missing’的三种Token注入方式对比 Clawdbot 是一个统一的 AI 代理网关与管理平台,旨在为开发者提供一个直观的界面来构建、部署和监控自主 AI 代理。通过集成的聊天界面、多模型支持和强大的扩展系统,Clawdbot 让 AI 代理的管理变得简单高效。 当你在 ZEEKLOG 星图镜像广场一键部署 Clawdbot 并集成本地运行的 qwen3:32b 模型后,大概率会遇到这样一个提示: disconnected (1008): unauthorized: gateway token missing (open a tokenized dashboard URL or paste token in Control UI settings) 这不是报错,也不是服务没起来—

By Ne0inhk
PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理

PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理

@[TOC](PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理) 🌺The Begin🌺点点关注,收藏不迷路🌺 一、模式(SCHEMA)概念解析 PostgreSQL中的模式(Schema)是数据库内部的一个命名空间,它包含表、视图、索引、序列、数据类型、函数、操作符等数据库对象。模式可以看作是数据库中的"文件夹",为数据库对象提供逻辑分组。 模式的核心特性: * 逻辑隔离:不同模式中的对象可以同名而不会冲突 * 权限控制:可以针对模式设置独立的访问权限 * 组织管理:将相关对象分组管理,提高可维护性 DatabaseSchema1Schema2Table1View1Function1 二、模式的应用场景 1. 多用户环境隔离 当多个用户共享一个数据库时,为每个用户创建独立的模式,避免命名冲突。 2. 应用程序隔离 第三方应用可以使用独立模式,避免与现有对象名称冲突。 3.

By Ne0inhk
Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案 前言 在前文中,我们探讨了 okay 在鸿蒙(OpenHarmony)端实现基础 Result 模式包装的实战。但在真正的“分布式微服务聚合”、“高并发资产对账”以及“具备自愈能力的 IoT 指令链”场景中。简单的 ok() 与 err() 判定往往不足以支撑起复杂的业务全景。面对需要同时并行发起 3 个 API 请求,并要求在“所有请求均成功时执行合并、任一请求失败时执行局部逻辑路由”的高阶需求。如果缺乏一套完善的异步结果映射与多级逻辑聚合机制。不仅会导致异步回调地狱(Callback Hell)在

By Ne0inhk
国产替代不掉链子:KingbaseES如何做到MySQL零感迁移

国产替代不掉链子:KingbaseES如何做到MySQL零感迁移

前言 在信创国产化的大趋势下,数据库作为数字基础设施的核心,其替代迁移工作成为企业数字化转型的关键环节。MySQL 作为国内企业应用最广泛的开源关系型数据库之一,凭借轻量、易用、生态完善的特点,在互联网、金融、政务、制造等多个行业落地生根。但不少企业在将 MySQL 向国产数据库迁移的过程中,却陷入了 “看似简单,实则踩坑” 的困境 —— 表面上的语法兼容背后,是 JSON 数据类型行为差异、事务隔离级别在高并发下的隐性适配问题、Group By 严格模式等细节带来的兼容性故障,甚至出现 “改一行代码,崩整个系统” 的极端情况。 业务方对迁移的核心顾虑,从来都不是 “能不能迁”,而是 “能不能稳迁、低成本迁、不影响业务迁”。本文将从 MySQL 迁移的核心痛点出发,深度解析电科金仓 KingbaseES 的 MySQL 兼容性技术实现,以及全流程迁移工程的落地能力,为企业 MySQL

By Ne0inhk