7-2 判断DFS序列的合法性 (25 分)
题目
给出一个有向图,请判断给出的深度优先遍历序列是否合法。 例如对于如下的图
0 1 4 2 5 3 和 0 2 5 1 4 3 和 3 2 5 1 4 0 都是合法的DFS序列,但0 2 3 1 4 5则不是合法的DFS序列。
输入格式
第一行给出顶点数 M 和边数 N,以空格分隔;顶点标号从 0 到 M−1。其中 M 不超过 100。 之后 N 行,每行按如下格式给出图中的各条边:
ID1 ID2
这表示从编号为ID1的顶点到编号为ID2的顶点的有向边。 之后一行给出整数 K,表示有 K 个序列待检验的序列。 接下来的 K 行,每行给出 M 个以空格分隔的整数表示DFS序列。
输出格式
对于每一个待检验序列,输出 Yes 表示合法,No 表示不合法。
基本思路
利用栈模拟dfs的过程,具体数据结构和详细思路请看代码注释。
代码
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=105;
int m,n,k;//顶点个数(编号0~m-1),边数,序列组数
int G[maxn][maxn];//利用邻接表来存储这张有向图。初始化所有顶点之间的距离为0,然后再从题目读入数据
bool visit[maxn];//下标为顶点编号,值表示这个顶点是否被访问过
int a[maxn];//存放一组dfs序列
//判断栈顶顶点是否有未访问的顶点可以到达
bool check(int v){
for(int i=0;i<m;i++){
if(G[v][i]==1&&visit[i]==false){
return true;
}
}
return false;
}
//判断序列是否合法
bool checklegal(){
stack<int> s;
for(int i=0;i<m;i++){
//序列首元素入栈,并置序列首元素为已访问,直接进入下一个for循环
if(i==0){
s.push(a[0]);
visit[a[0]]=true;
continue;
}
//如果当前栈顶元素和a[i]之间有边,将a[i]入栈并置a[i]为已访问,直接进入下一个for循环
/*否则,当栈非空时循环:
如果栈顶元素和a[i]之间有边,将a[i]入栈并置a[i]为已访问,退出while循环,进入下一个for循环(第一个while循环中,顶点top正是因为与a[i]没有边才到这一步,所以肯定通不过第一个if,但是接受第二个if的检验;后序的while循环中s.top()均需要接受两个if的检验)
到了这一步,说明栈顶元素和a[i]之间没有边,如果栈顶元素有未访问的顶点可以到达,说明这是非法序列,直接返回false,函数结束
到了这一步,既说明栈顶元素和a[i]之间没有边,又说明栈顶元素没有未访问的顶点可以到达,这时候就需要把栈顶元素出栈,进入下一个while循环
*/
int top=s.top();
int v=a[i];
if(G[top][v]==1){
s.push(v);
visit[v]=1;
}else{
//当栈非空时循环:在栈中寻找一个和a[i]有边的顶点,作为栈顶元素
while(!s.empty()){
//
if(G[s.top()][v]){
s.push(v);
visit[v]=1;
break;
}
//
if(check(s.top()))
return false;
//
s.pop();
}
//如果栈空了,说明a[i]属于不同的连通分量,将a[i]入栈,并置a[i]为已访问,进入下一个for循环
if(s.empty()){
s.push(v);
visit[v]=1;
}
}
}
//如果上述的步骤都没有发生问题,说明该序列是合法序列,返回true
return true;
}
int main(){
cin>>m>>n;
for(int i=0;i<n;i++){
int ans1,ans2;
cin>>ans1>>ans2;
G[ans1][ans2]=1;
}
cin>>k;
while(k--){
//读入本组d序列(每次读入一组数据会覆盖上一组数据,相当于重新初始化了数组a)
for(int i=0;i<m;i++) cin>>a[i];
//重新初始化数组visit
fill(visit,visit+maxn,false);
//判断序列是否合法,根据函数返回的结果做出相应的输出
if(checklegal()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}