P1305 新二叉树

记录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代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

核心解题思路

这道题是一道非常经典的二叉树基础与递归遍历问题。

  1. 问题转化(字符到数字的映射)
    题目输入的是字母形式的节点(如 abc),在 C++ 中如果直接用字符作为数组下标会比较麻烦。因此,我们利用 ASCII 码的特性,将字母 a~z 映射为数字 0~25(即 u - 'a')。这样,我们就可以使用简单的整型数组来存储每个节点的左右孩子。

  2. 算法设计(前序遍历的递归实现)
    前序遍历的规则是“根节点 →→ 左子树 →→ 右子树”。在代码中,我们通过递归函数来实现:

    • 首先访问当前节点(输出当前节点的字母)。
    • 然后递归调用函数去遍历左子树。
    • 最后递归调用函数去遍历右子树。
      题目保证第一行输入的必定是根节点,因此我们只需从根节点开始调用前序遍历函数即可。

代码分块详细解释

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 瓶颈导致超时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值