7-2 判断DFS序列的合法性 (25 分)

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;
}
### DFS序的概念 DFS序是指在深度优先搜索(Depth First Search, DFS)过程中访问节点的时间顺序记录。通常为三种时间戳: 1. **进入时间**:首次访问某节点时的时间标记。 2. **退出时间**:完成对该节点及其子树的遍历并返回上一层时的时间标记。 3. **路径序列**:按照实际访问次序形成的节点序列。 这种序列化方式能够帮助我们快速判断某些关系,例如祖先-后代关系、子树范围等[^2]。 --- ### DFS序的实现 以下是基于图结构的DFS序实现方法: #### 方法一:递归实现 通过递归函数,在每次访问新节点时记录其进入时间和退出时间,并将其加入路径序列。 ```cpp #include <bits/stdc++.h> using namespace std; vector<vector<int>> adj; vector<int> enterTime, exitTime, pathOrder; int timer = 0; void dfs(int u) { enterTime[u] = ++timer; // 记录进入时间 pathOrder.push_back(u); // 将当前节点加入路径序列 for (auto &v : adj[u]) { if (!enterTime[v]) { // 如果未访问过 dfs(v); } } exitTime[u] = ++timer; // 记录退出时间 } // 主程序调用 int main() { int n, m; cin >> n >> m; adj.resize(n + 1), enterTime.assign(n + 1, 0), exitTime.assign(n + 1, 0); while (m--) { int a, b; cin >> a >> b; adj[a].push_back(b); } for (int i = 1; i <= n; ++i) { if (!enterTime[i]) { dfs(i); } } cout << "Path Order: "; for(auto node:pathOrder){ cout<<node<<" "; // 输出路径序列 } } ``` 上述代码实现了基本的DFS过程,并生成了路径序列 `pathOrder` 和对应的进入/退出时间数组 `enterTime`, `exitTime`[^3]。 --- #### 方法二:迭代实现 对于空间有限的情况,可以通过显式维护一个模拟递归行为。 ```cpp stack<pair<int, bool>> stk; // pair<节点编号, 是否已处理子节点> stk.emplace(1, false); // 假设从节点1开始 while(!stk.empty()){ auto &[u, processed] = stk.top(); if(!processed){ enterTime[u] = ++timer; pathOrder.push_back(u); processed = true; // 标记为已处理 for(auto it=adj[u].rbegin();it!=adj[u].rend();++it){ // 反向压入子节点 if(!enterTime[*it]){ stk.emplace(*it, false); } } }else{ exitTime[u] = ++timer; stk.pop(); } } ``` 此版本避免了深递归可能导致的溢出问题。 --- ### DFS序的应用 1. **验证合法性**:给定一颗树以及部缺失的DFS序,可通过重建完整的DFS序来检验输入是否合理。 2. **区间查询优化**:利用DFS序将树上的操作转化为线性表上的操作,从而加速动态规划或RMQ等问题求解速度。 3. **拓扑排序辅助工具**:虽然主要讨论的是BFS-based Kahn算法用于有向无环图(DAG),但在特殊情况下也可以借助DFS得到逆后序作为最终结果之一[^1]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值