当前位置: 首页 > news >正文

PTA数据结构实战:层次遍历巧解二叉树叶结点输出

1. 从问题理解到解题思路

第一次看到PTA上这道二叉树题目时,我也被题目描述唬住了。题目要求按从上到下、从左到右的顺序输出所有叶结点,这不就是典型的层次遍历(BFS)应用场景吗?但仔细分析输入格式后,我发现有几个关键点需要注意:

输入格式中,每个结点的左右孩子是用字符'-'表示的,这意味着我们需要处理字符到数字的转换。另外,题目给出的结点编号是从0开始的连续整数,这提示我们可以用静态数组来存储树结构,既节省空间又方便查找。

在实际解题时,我总结出三个关键步骤:

  1. 从输入数据中找到根节点
  2. 根据输入数据构建二叉树
  3. 使用层次遍历找出所有叶结点

这种分步解决的方法,把一个大问题拆解成几个小问题,每个小问题都有明确的解决思路,大大降低了编码难度。这也是我在数据结构学习中总结出的重要经验——化整为零,逐个击破

2. 静态数组存储与根节点查找技巧

在处理树的输入数据时,我发现一个很实用的技巧:用标记数组找根节点。因为除了根节点外,其他所有节点都会作为某个节点的孩子出现。基于这个观察,我们可以创建一个长度为N的标记数组,初始值全为0。

具体实现时,我遍历每个节点的左右孩子,如果孩子存在(不是'-'),就把对应下标的标记数组位置设为1。最后,标记数组中仍为0的位置就是根节点的编号。这个方法的时间复杂度是O(N),非常高效。

int FindHead(ThreeArr TArr[], int n) { if (n == 1) return 0; int Arr[n]; for(int i = 0; i < n; i++) { Arr[i] = 0; } for(int i = 0; i < n; i++) { if(TArr[i].LNode != '-') { Arr[TArr[i].LNode-'0'] = 1; } if(TArr[i].RNode != '-') { Arr[TArr[i].RNode-'0'] = 1; } } for(int i = 0; i < n; i++) { if(Arr[i] == 0) { return i; } } return -1; // 理论上不会执行到这里 }

这里有个细节需要注意:字符'0'到'9'的ASCII码是连续的,所以可以用child-'0'的方式将字符转换为数字。这个小技巧在处理字符型数字输入时非常实用。

3. 递归构建二叉树

找到根节点后,接下来就是构建二叉树。我采用了递归方法,因为递归的思路最符合树结构的本质特性。每个节点的构建过程都是相同的:创建节点,然后递归创建其左右子树。

TreeNode *BuildTree(int head, ThreeArr TArr[]) { TreeNode *T = (TreeNode *)malloc(sizeof(TreeNode)); T->val = head; T->right = T->left = NULL; if(TArr[head].LNode != '-') T->left = BuildTree(TArr[head].LNode - '0', TArr); if(TArr[head].RNode != '-') T->right = BuildTree(TArr[head].RNode - '0', TArr); return T; }

在实际编码时,我犯过一个错误:忘记处理孩子为'-'的情况,导致程序访问非法内存。这个教训让我明白,边界条件的检查在树结构操作中尤为重要。递归虽然简洁,但必须确保有明确的终止条件,否则很容易导致栈溢出。

4. 层次遍历实现与叶结点判断

题目要求按从上到下、从左到右的顺序输出叶结点,这正是**层次遍历(BFS)**的典型应用。与深度优先搜索(DFS)不同,BFS需要使用队列来辅助实现。

我的实现思路是:

  1. 将根节点入队
  2. 循环直到队列为空:
    • 出队一个节点
    • 检查是否是叶结点(左右孩子都为空)
    • 将非空左右孩子入队
void LevelOrder(TreeNode *T) { int flag = 0; if(T) { TreeNode *queue[100]; int left = 0, right = 0; queue[right++] = T; while (left < right) { TreeNode *bt = queue[left++]; if(bt->left == NULL && bt->right == NULL) { if(flag == 0) { printf("%d", bt->val); flag++; } else { printf(" %d", bt->val); } } if(bt->left) queue[right++] = bt->left; if(bt->right) queue[right++] = bt->right; } } }

这里有个输出格式的小技巧:使用flag变量控制空格的输出,确保第一个数字前和最后一个数字后没有多余空格。这种细节处理在PTA等在线评测系统中非常重要,可能决定你的代码是否能通过所有测试用例。

5. 完整代码实现与测试

将上述各个部分组合起来,就得到了完整的解决方案。为了验证代码的正确性,我用题目给出的样例进行了测试:

输入:

8 1 - - - 0 - 2 7 - - - - 5 - 4 6

输出:

4 1 5

这个结果与题目给出的样例输出一致,说明我们的实现是正确的。完整的代码如下:

#include <stdio.h> #include <stdlib.h> typedef struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; } TreeNode; typedef struct ThreeArr { char LNode; char RNode; } ThreeArr; int FindHead(ThreeArr TArr[], int n) { if (n == 1) return 0; int Arr[n]; for(int i = 0; i < n; i++) { Arr[i] = 0; } for(int i = 0; i < n; i++) { if(TArr[i].LNode != '-') { Arr[TArr[i].LNode-'0'] = 1; } if(TArr[i].RNode != '-') { Arr[TArr[i].RNode-'0'] = 1; } } for(int i = 0; i < n; i++) { if(Arr[i] == 0) { return i; } } return -1; } TreeNode *BuildTree(int head, ThreeArr TArr[]) { TreeNode *T = (TreeNode *)malloc(sizeof(TreeNode)); T->val = head; T->right = T->left = NULL; if(TArr[head].LNode != '-') T->left = BuildTree(TArr[head].LNode - '0', TArr); if(TArr[head].RNode != '-') T->right = BuildTree(TArr[head].RNode - '0', TArr); return T; } void LevelOrder(TreeNode *T) { int flag = 0; if(T) { TreeNode *queue[100]; int left = 0, right = 0; queue[right++] = T; while (left < right) { TreeNode *bt = queue[left++]; if(bt->left == NULL && bt->right == NULL) { if(flag == 0) { printf("%d", bt->val); flag++; } else { printf(" %d", bt->val); } } if(bt->left) queue[right++] = bt->left; if(bt->right) queue[right++] = bt->right; } } } int main() { int n; scanf("%d", &n); getchar(); ThreeArr ta[n]; for(int i = 0; i < n; i++) { scanf("%c %c", &ta[i].LNode, &ta[i].RNode); getchar(); } int head = FindHead(ta, n); TreeNode *Tree = BuildTree(head, ta); LevelOrder(Tree); return 0; }

在实际编码过程中,我发现有几个常见错误需要注意:

  1. 忘记释放动态分配的内存(虽然在这个简单程序中影响不大)
  2. 数组越界访问,特别是在处理字符到数字转换时
  3. 输入处理时忘记用getchar()吸收换行符

6. 算法优化与扩展思考

虽然这个解法已经能够很好地解决问题,但我们还可以进一步思考可能的优化空间。例如,是否可以不用构建完整的二叉树结构,直接在输入数据上进行层次遍历?

经过分析,我认为是可行的。我们可以维护一个队列,存储待处理的节点编号,然后直接从输入数组中查找其左右孩子。这种方法可以节省构建树结构的时间和空间,但代码可读性可能会降低。

另一个值得思考的问题是:如果树非常大(比如节点数超过10^5),我们的静态数组队列可能不够用。这时应该改用动态扩容的队列,或者使用链表实现的队列。

在实际工程项目中,我们还需要考虑更多的边界情况,比如:

  • 空树的处理
  • 所有节点都是叶结点的情况
  • 输入数据不合法的情况(如形成环)

这些问题虽然在这个PTA题目中没有出现,但在实际开发中都需要考虑周全。这也是算法题目和实际工程问题的一个重要区别。

http://www.jsqmd.com/news/827464/

相关文章:

  • OpenMV4 H7 + MSP430F5529 循迹小车避坑指南:从色块阈值调试到WiFi图传稳定连接
  • 告别源码编译焦虑:我的zlib-1.2.11和libpng-1.6.36通用编译脚本进化史
  • 【USB笔记】配置描述符:从协议解析到实战抓包
  • 联想E14升级BIOS踩坑实录:改开机Logo时,那个‘安全回滚预防’报错怎么破?
  • 2026年薪酬绩效与组织设计十大知名咨询公司推荐,靠谱机构排名及核心优势 - 远大方略管理咨询
  • 从英文界面到母语设计:FigmaCN如何改变你的设计工作流
  • 闲置武商一卡通如何快速回收?五大技巧值得收藏! - 团团收购物卡回收
  • Windows驱动存储清理指南:用DriverStore Explorer找回被占用的磁盘空间
  • 证件照怎样换底色?证件照背景颜色怎么改?2026 实测常用APP与微信小程序完全指南 - AI测评专家
  • ADC0809CCN实战指南:从引脚解析到51单片机驱动
  • 终极LXMusic音源配置指南:5步实现专业级音乐播放解决方案
  • 学妹问降AI率工具选哪个性价比最高?4款降AI软件1万字花多少过AIGC检测
  • 激光位移传感器安装:从能用迈向精准的关键工艺与避坑指南
  • 从空调遥控到智能家居:深入浅出聊聊NEC红外协议的那些‘潜规则’与兼容性坑
  • 终极指南:如何用Reset-Windows-Update-Tool快速修复Windows更新故障
  • 终极解决方案:3分钟实现QQ音乐加密文件自由转换
  • 浏览器扩展开发实战:用Ctrl+Enter优化AI对话工具交互体验
  • 大语言模型硬件加速器的容错技术与实践
  • 面试准备
  • PSIM 9.0 手把手教学:从零搭建直流电机双闭环调速模型(附完整代码与波形分析)
  • LabVIEW玩转ST-Link:除了烧录,这些CLI隐藏命令让你的调试效率翻倍
  • 酒店一次性用品采购:五个常见问题与供应商筛选参考 - 资讯速览
  • Transformer架构与混合专家系统(MoE)的技术演进与应用
  • LoRa项目实战:手把手教你为ESP32选配和焊接天线(从PCB到信号测试)
  • 高光谱遥感动态嵌入与语义交互技术解析
  • 量子退火求解Steiner旅行商问题的优化方法
  • STM32F407的GPIO不够用?手把手教你用软件SPI驱动RC522读卡器
  • MoviePilot批量重命名:3步解决媒体库混乱难题
  • visual studio 的 snippet 代码片段模板样式
  • 3种高效方法实现抖音无水印视频下载:从原理到实战全解析