题目坑点还挺多,因此写个题解记录一下。
坑点1:单向边,dfs u到v的时候与双向边不同即不需要加判断v!=fa,特判节点孩子情况的时候也与双向边不同
坑点2:没告诉根节点,需要自己找入度为0的点作为根节点
坑点3:节点单孩子的情况特殊,孩子节点值大于自身值才作为左孩子,否则为右孩子
坑点4:节点有两个孩子的情况,左右孩子的判断是基于两个孩子的节点值大小判断
我习惯使用vector作邻接表建边,这样先序和后序使用dfs非常容易实现,而中序dfs需要多些判断。
对于节点两个孩子的情况,我们对邻接表adj[i]进行从小到大排序,i取遍1~n,这样在dfs遍历出边的时候,第一个遍历到的一定是左孩子节点,第二个遍历到的是右孩子节点。
#include <iostream>
#include <vector>
#include <algorithm>
void solve(){
int n;
std::cin >> n;
std::vector<std::vector<int>> adj(n+1);//邻接表
std::vector<int> in(n+1);//入度
for(int i = 1; i < n; i++){
int u,v;
std::cin >> u >> v;
adj[u].push_back(v);//建立u->v的边
in[v]++;//v的入度+1
}
int root = -1;//根节点
for(int i = 1; i <= n; i++){
if(in[i] == 0){
root = i;//根节点为入度为0的点
}
}
for(int i = 1; i <= n; i++){//对邻接表排序
std::sort(adj[i].begin(),adj[i].end());
}
auto dfs1 = [&](auto &&self1,int u)->void {
//先序:根 左 右
std::cout << u << " ";
for(auto v:adj[u]){
self1(self1,v);
}
};
dfs1(dfs1,root);//从根节点开始先序遍历
std::cout << "\n";
auto dfs2 = [&](auto &&self2,int u)->void {
//中序:左 根 右
if(adj[u].size() == 0){//u为叶子节点,直接打印u并return
std::cout << u << " ";
return;
}
if(adj[u].size() == 1){//u只有一个孩子
int v = adj[u].front();//v为唯一孩子
if(v > u){//若v为左孩子,先访问左孩子,再访问根
self2(self2,v);
std::cout << u << " ";
}else{//若v为右孩子,先访问根,再访问右孩子
std::cout << u << " ";
self2(self2,v);
}
return;
}
//u有两个孩子
bool hasLeft = false;//表示左孩子是否已经访问完毕
for(auto v:adj[u]){
if(hasLeft){//若左孩子已经访问结束,则需要先访问根
std::cout << u << " ";
}
self2(self2,v);
hasLeft = true;//第一次循环结束后,该标志位会被置为true
}
};
dfs2(dfs2,root);//从根节点开始中序遍历
std::cout << "\n";
auto dfs3 = [&](auto &&self3,int u)->void {
//后序:左 右 根
for(auto v:adj[u]){
self3(self3,v);
}
std::cout << u << " ";
};
dfs3(dfs3,root);//从根节点开始后序遍历
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T = 1;
// std::cin >> T;
for(; T--;){
solve();
}
return 0;
}

京公网安备 11010502036488号