CHAPTER_9 提高篇(3)——数据结构(2)
9.3.4 从树的遍历看DFS和BFS
深度优先搜索与先根遍历
回忆树的先根遍历,我们会发现先根遍历就是DFS的过程。实际上,对所有合法的DFS过程,都哦可以把他画成树的形式,此时递归边界等价于树的叶子节点,而分岔口等价于树的非叶子节点。
于是我们可以从中得到一些启发:碰到一些可以用DFS的题目,不妨把一些状态作为树的结点。然后问题就会转化为对树的先根遍历。如果想要得到树的某些信息,也可以借用DNFS以深度作为第一关键词进行遍历。例如求解叶子节点的带权路径和时就可以把到达叶子节点作为一条路径结束的判断。
广度优先搜索与层序遍历
树的层序遍历,和BFS同样有着相同的思想。对于所有合法的BFS求解过程,也可以像DFS那样画出一棵树,并且将其转化为树的层次遍历。
下面我们通过一个例题来练习这一思想。
题目:
给定一棵树和每个节点的权值,求所有从根节点到叶子节点的路径中,使得每条路径上的节点权值之和等于给定常数S的路径。如果有多条这样的路径,则按路径非递增的顺序输出。其中路径大小是指,如果两条分别为a1-a2-...-ai-an与b1-b2-...-bi-bm,且有a1=b1,a2=b2,...,ai-1=bi-1成立,但ai>bi,则称第一条路径比第二条路径大。
输入格式:
每个输入包含一个测试用例。每个测试用例在第一行给出节点个数N,非叶子节点的个数M,给定常数S。其中0<N<=100,M<N,S<2^30。第二行给出N个正数,对应着N个节点的权重。接下来有M行,每行给出每个非叶子节点的具体信息:节点编号、子节点个数、每个子节点的编号。
输出格式:
输出权值和等于S的所有路径。输出路径上每个节点的权值即为输出路径(每个权值用空格分隔),每个路径占一行,并按非递增排序。
输入样例:
20 9 24
10 2 4 3 5 10 2 18 9 7 2 2 1 3 12 1 8 6 2 2
00 4 01 02 03 04
02 1 05
04 2 06 07
03 3 11 12 13
06 1 09
07 2 08 10
16 1 15
13 3 14 16 17
17 2 18 19
输出样例:
10 5 2 7
10 4 10
10 3 3 6 2
10 3 3 6 2
思路:
我们采用树的静态写法来存储节点,并根据输入格式来将节点连接成树。然后设置int型数组path记录递归过程中产生的节点编号。然后开始DFS递归,参数有3个:当前访问节点index、当前路径节点个数nodeNum、当前权值和sum。递归过程如下:
(1)如果sum>S,直接退出;
(2)如果sum==S且当前节点为叶子节点,说明已经达到符合条件的路径,输出path中所有数 据;如果sum==S但当前不是叶子节点,直接退出;
(3)如果sum<S,说明还可以往下递归。遍历当前节点的所有子节点,对每一个子节点将其存入 path,然后往下递归。
通过上面的算法,我们能将所有权值和等于S的路径输出。但是,我们还没有解决路径的排序输出问题。路径排序输出的方法十分巧妙,我们在读入每个节点的所有子节点之后(即输入每行节点的具体信息之后),对该节点的vector进行非递减排序。通过这么排序,DFS的输出路径就自然而然地变为非递减排序路径了。(不妨手动模拟vector排序后的DFS体会其过程)
参考代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=101;
int n,m,s; //节点个数、非叶子节点个数、常数S
int path[maxn]={0};
struct Node { //定义树节点
int data;
vector<int> child;
}node[maxn];
bool cmp(int a,int b) {
return node[a].data>node[b].data;
}
void DFS(int index,int nodeNum,int sum) {
sum+=node[index].data; //更新当前和
nodeNum++; //结点数+1
path[nodeNum]=index; //将当前节点加入路径
if(sum>s) //当前和大于s,退出
return;
else if(sum==s) { //当前和等于s
if(node[index].child.size()!=0) { //当前非叶子节点
return;
}
else { //当前叶子节点
for(int i=1;i<=nodeNum;i++) { //控制格式输出path中的节点权值
if(i==nodeNum)
cout<<node[path[i]].data<<endl;
else
cout<<node[path[i]].data<<' ';
}
return;
}
}
else { //当前和小于叶子节点,往下递归
for(int i=0;i<node[index].child.size();i++) { //遍历所有子节点
DFS(node[index].child[i],nodeNum,sum); //递归至下一轮
}
}
}
int main() {
int id,k,tmp; //id接收节点编号,k记录节点孩子个数
cin>>n>>m>>s;
for(int i=0;i<n;i++) { //输入每个节点的权重
cin>>node[i].data;
}
for(int i=0;i<m;i++) { //输入每个节点的具体信息
cin>>id>>k;
for(int j=0;j<k;j++) {
cin>>tmp;
node[id].child.push_back(tmp);
}
sort(node[id].child.begin(),node[id].child.end(),cmp); //对子节点按权值排序
}
DFS(0,0,0); //从根节点开始,初始状态节点个数0,和为0
return 0;
}