洛谷 P1305:新二叉树 ← DFS + 哈希表优化
【题目来源】
https://www.luogu.com.cn/problem/P1305
【题目描述】
输入一串二叉树,输出其前序遍历。
【输入格式】
第一行为二叉树的节点数 n。(1≤n≤26)
后面 n 行,第一个字母为节点,后两个字母分别为其左右儿子。特别地,数据保证第一行读入的节点必为根节点。
空节点用 * 表示。
【输出格式】
二叉树的前序遍历。
【输入样例】
6
abc
bdi
cj*
d**
i**
j**
【输出样例】
abdicj
【数据范围】
1≤n≤26
【算法分析】
● 在 C++ 中,std::vector 是一个动态数组容器,默认构造时其大小为 0,且不包含任何元素。直接使用下标运算符 tr[i] 访问未分配内存的位置会导致未定义行为(通常是段错误 Segmentation Fault 或程序崩溃),因为该内存地址并不属于当前 vector 的管理范围。修正方法之一为在访问前,利用 tr.resize(n); 调整 vector 的大小。
● 算法代码一:纯 vector 写法,时间复杂度为 O(n^2) → 不能处理重复字符
本题若用如下纯 vector 的写法,时间复杂度为 O(n^2),能过。但要注意,其仅适用于 n 很小(n<1000)情况。
#include <bits/stdc++.h>
using namespace std;
int n;
vector<string> tr; //tree
void dfs(char x) {
if(x=='*') return;
for(int i=0; i<n; i++) {
if(tr[i][0]==x) {
cout<<tr[i][0];
dfs(tr[i][1]);
dfs(tr[i][2]);
}
}
}
int main() {
cin>>n;
tr.resize(n);
for(int i=0; i<n; i++) {
cin>>tr[i];
}
dfs(tr[0][0]);
return 0;
}
/*
in:
6
abc
bdi
cj*
d**
i**
j**
out:
abdicj
*/
在上述纯 vector 的实现代码中,为什么必须加 if(tr[i]==x)?这个判断语句的核心作用是建立“节点值”与“存储位置”之间的映射关系。
● 算法代码二:哈希表写法,时间复杂度为 O(n) → 不能处理重复字符
本代码利用哈希表(Hash Map)实现快速索引,从而将算法效率提升了一个数量级。这是处理树形结构遍历问题时,常用的标准优化手段。
#include <bits/stdc++.h>
using namespace std;
unordered_map<char,int> indexMap;
vector<string> tr;
int n;
void dfs(char x) {
if(x=='*') return;
int i=indexMap[x];
cout<<tr[i][0];
dfs(tr[i][1]);
dfs(tr[i][2]);
}
int main() {
cin>>n;
tr.resize(n);
for(int i=0; i<n; i++) {
cin>>tr[i];
indexMap[tr[i][0]]=i;
}
dfs(tr[0][0]);
return 0;
}
/*
in:
6
abc
bdi
cj*
d**
i**
j**
out:
abdicj
*/
但要注意,如上所示的哈希表优化的写法,要求每行输入的字符串中不能有重复的字符。这是因为,只要输入的字符串中有重复字符,哈希表映射 char → index 就会被覆盖,会直接报错!也就是说,无重复字符时可以用哈希表,有重复字符时禁止用哈希表,必须用“下标递归 + 遍历找孩子”。
● 算法代码三:结构体写法,时间复杂度为 O(n^2) → 能够处理重复字符
鉴于此,就有了如下更通用的代码,既适用于无重复字符的情况,又适用于有重复字符的情况。但是,时间复杂度达到 O(n^2)。
#include <bits/stdc++.h>
using namespace std;
const int N=30;
struct Node {
char val;
int le,ri;
} tr[N]; //tree
void dfs(int u) {
if(u==-1) return;
cout<<tr[u].val;
dfs(tr[u].le);
dfs(tr[u].ri);
}
int main() {
vector<string> v;
int n;
cin>>n;
v.resize(n);
for(int i=0; i<n; i++) {
cin>>v[i];
tr[i].val=v[i][0];
tr[i].le=-1;
tr[i].ri=-1;
}
for(int i=0; i<n; i++) {
char lc=v[i][1];
char rc=v[i][2];
for(int j=0; j<n; j++) {
if(j!=i && v[j][0]==lc) {
tr[i].le=j;
break;
}
}
for(int j=0; j<n; j++) {
if(j!=i && v[j][0]==rc) {
tr[i].ri=j;
break;
}
}
}
dfs(0);
return 0;
}
/*
in:
6
aba
bdi
cj*
d**
i**
j**
out:
abdi
*/
【算法代码】→ 结构体写法,时间复杂度为 O(n) 。能够处理重复字符。
(1)pos 是一个二维数组,大小开 256 → 因为 ASCII 码一共就 256 个字符。
(2)pos 是一个 “字符 → 下标列表” 的映射表。本质上,pos 是二维数组。第一维:字符的 ASCII 码(0~255),第二维:这个字符出现的所有下标。
#include <bits/stdc++.h>
using namespace std;
const int N=30;
struct Node {
char val;
int le,ri;
} tr[N];
/*character -> subscripts
Supports repeated characters*/
vector<vector<int>> pos;
void dfs(int u) {
if(u==-1) return;
cout<<tr[u].val;
dfs(tr[u].le);
dfs(tr[u].ri);
}
int main() {
pos.resize(256); //ASCII code
vector<string> v;
int n;
cin>>n;
v.resize(n);
for(int i=0; i<n; i++) {
cin>>v[i];
tr[i].val=v[i][0];
tr[i].le=-1;
tr[i].ri=-1;
pos[v[i][0]].push_back(i); //key
}
for(int i=0; i<n; i++) { //find son
char lson=v[i][1];
char rson=v[i][2];
for(auto x:pos[lson]) {
if(x!=i) {
tr[i].le=x;
break;
}
}
for(auto x:pos[rson]) {
if(x!=i) {
tr[i].ri=x;
break;
}
}
}
dfs(0);
return 0;
}
/*
in:
6
aba
bdi
cj*
d**
i**
j**
out:
abdi
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/160587719
https://blog.csdn.net/hnjzsyjyj/article/details/148295326
https://blog.csdn.net/hnjzsyjyj/article/details/160534326
https://blog.csdn.net/hnjzsyjyj/article/details/118736059
https://blog.csdn.net/hnjzsyjyj/article/details/156342794
https://blog.csdn.net/hnjzsyjyj/article/details/156341089
https://blog.csdn.net/hnjzsyjyj/article/details/128103062

浙公网安备 33010602011771号