记录161
#include<bits/stdc++.h> // 引入万能头文件,包含所有常用的标准库
using namespace std; // 使用标准命名空间
int n; // 定义全局变量n,表示二叉树的结点总数
int l[30],r[30]; // 定义大小为30的int数组,用0-25的下标对应字母a-z的左右孩子
char root_char; // 用来保存整棵树的根节点字符
// 前序遍历函数:根 -> 左 -> 右(参数u是转化后的数字下标)
void preOrder(int u){
if(u==-1)return; // 递归终止条件:如果下标为-1(代表'*'空结点),直接返回
cout<<char(u+'a'); // 将数字下标还原为字母并输出(0+'a'='a')
preOrder(l[u]); // 递归遍历左子树
preOrder(r[u]); // 递归遍历右子树
}
int main(){ // 主函数入口
ios::sync_with_stdio(false); // 关闭cin与stdio的同步,加快输入输出速度
cin.tie(0); // 解除cin与cout的绑定,进一步加快IO效率
cin>>n; // 输入二叉树的结点总数n
char u,left,right; // 定义临时字符变量,用来接收输入的根、左、右结点
for(int i=0;i<n;i++){ // 循环n次,读入每个结点的子结点信息
cin>>u>>left>>right; // 读入当前结点和它的左右儿子
if(i==0)root_char=u; // 题目保证第一行读入的节点必为根节点,将其保存
// 核心映射:将字母转化为数字下标。如果是'*',映射为-1作为空结点的标记
l[u-'a']=(left=='*')?-1:(left-'a');
r[u-'a']=(right=='*')?-1:(right-'a');
}
preOrder(root_char-'a'); // 将根节点字符转化为数字下标,开始进行前序遍历
return 0; // 主函数正常结束,返回0
}
题目传送门
https://www.luogu.com.cn/problem/P1305
前言
我是一名专注信奥赛(CSP-J/S、GESP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道非常经典的二叉树基础与递归遍历问题。
-
问题转化(字符到数字的映射):
题目输入的是字母形式的节点(如a,b,c),在 C++ 中如果直接用字符作为数组下标会比较麻烦。因此,我们利用 ASCII 码的特性,将字母a~z映射为数字0~25(即u - 'a')。这样,我们就可以使用简单的整型数组来存储每个节点的左右孩子。 -
算法设计(前序遍历的递归实现):
前序遍历的规则是“根节点 →→ 左子树 →→ 右子树”。在代码中,我们通过递归函数来实现:- 首先访问当前节点(输出当前节点的字母)。
- 然后递归调用函数去遍历左子树。
- 最后递归调用函数去遍历右子树。
题目保证第一行输入的必定是根节点,因此我们只需从根节点开始调用前序遍历函数即可。
代码分块详细解释
1. 头文件、全局变量与数组定义
#include<bits/stdc++.h> // 引入万能头文件,包含所有常用的标准库
using namespace std; // 使用标准命名空间
int n; // 定义全局变量n,表示二叉树的结点总数
int l[30], r[30]; // 定义大小为30的int数组,用0-25的下标对应字母a-z的左右孩子
char root_char; // 用来保存整棵树的根节点字符
- 详细分析:由于题目说明节点由字母
a~z组成,最多 26 个节点,所以定义大小为 30 的数组l和r绰绰有余。l[i]存储字母i+'a'的左孩子对应的数字下标,r[i]存储右孩子对应的数字下标。全局变量root_char用于记录树的根节点。
2. 核心逻辑:前序遍历递归函数
// 前序遍历函数:根 -> 左 -> 右(参数u是转化后的数字下标)
void preOrder(int u){
if(u == -1) return; // 递归终止条件:如果下标为-1(代表'*'空结点),直接返回
cout << char(u + 'a'); // 将数字下标还原为字母并输出(0+'a'='a')
preOrder(l[u]); // 递归遍历左子树
preOrder(r[u]); // 递归遍历右子树
}
- 详细分析:这是二叉树遍历的灵魂。
- 边界处理:输入中的
*代表空节点。我们在建树时将*映射为-1。当递归遇到-1时,说明当前分支为空,直接return回溯。 - 访问根节点:
cout << char(u + 'a');将数字下标还原为对应的字母并立即输出,这完美契合了前序遍历“先访问根”的规则。 - 递归子树:随后依次对左孩子
l[u]和右孩子r[u]发起递归调用。
- 边界处理:输入中的
3. 主函数:数据读入与建树映射
int main(){
ios::sync_with_stdio(false); // 关闭cin与stdio的同步,加快输入输出速度
cin.tie(0); // 解除cin与cout的绑定,进一步加快IO效率
cin >> n; // 输入二叉树的结点总数n
char u, left, right; // 定义临时字符变量,用来接收输入的根、左、右结点
for(int i = 0; i < n; i++){ // 循环n次,读入每个结点的子结点信息
cin >> u >> left >> right; // 读入当前结点和它的左右儿子
if(i == 0) root_char = u; // 题目保证第一行读入的节点必为根节点,将其保存
// 核心映射:将字母转化为数字下标。如果是'*',映射为-1作为空结点的标记
l[u - 'a'] = (left == '*') ? -1 : (left - 'a');
r[u - 'a'] = (right == '*') ? -1 : (right - 'a');
}
- 详细分析:这部分完成了从“字符描述”到“数组存储”的转换。
- 利用三目运算符
(条件) ? 值1 : 值2,非常优雅地处理了空节点*的映射。如果输入是*,则赋值为-1;否则,利用left - 'a'将字母转化为对应的数字下标。 - 题目明确说明第一行读入的必为根节点,所以
i==0时记录根节点即可。
- 利用三目运算符
4. 启动遍历与程序结束
preOrder(root_char - 'a'); // 将根节点字符转化为数字下标,开始进行前序遍历
return 0; // 主函数正常结束,返回0
}
- 详细分析:将根节点的字符转化为数字下标后,作为参数传入
preOrder函数,启动整棵树的遍历。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 字符映射 | u - 'a' | 将字母节点转化为 0~25 的整数下标 | 避免了使用复杂的 map 或字符数组下标,简化了树的存储 |
| 空节点标记 | (left=='*') ? -1 : ... | 将代表空节点的 * 映射为 -1 | 为递归遍历提供了明确的终止条件(边界处理) |
| 根节点记录 | if(i==0) root_char=u | 锁定遍历的起始点 | 确保前序遍历能从正确的根节点开始 |
| 前序遍历 | cout << ...; preOrder(l); preOrder(r); | 严格按照“根 →→ 左 →→ 右”的顺序递归 | 完美实现了二叉树的前序遍历逻辑 |
| IO加速 | ios::sync_with_stdio(false) | 关闭 C++ 与 C 的标准流同步 | 防止在递归输出大量字符时因 IO 瓶颈导致超时 |

1013

被折叠的 条评论
为什么被折叠?



