zoukankan      html  css  js  c++  java
  • 拓扑排序 (DFS和BFS及判断是否有环)

    一、什么是拓扑排序?

    在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

    • 每个顶点出现且只出现一次。
    • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

    有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

    拓扑排序其实还是挺奇妙的,就是解决谁先谁后的问题

    例如,下面这个图:

    它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:
    1. 从 DAG 图中选择一个没有前驱(即入度为0)的顶点并输出。

    2. 从图中删除该顶点和所有以它为起点的有向边。

    3. 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止

    于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。

    通常,一个有向无环图可以有一个或多个拓扑排序序列。这是因为可能同时存在多个入度为0的结点,这时,先处理哪个都是可以的。

    二. 拓扑排序用来干什么?

    • 判断是否有环
    • 求DAG中的最长链

    拓扑排序有两种方式,就是bfs和dfs,一般书中介绍的大多数是bfs,大家就以为拓扑排序只有一种办法,其实是不对的。

    参考链接 :https://blog.csdn.net/weixin_43918531/article/details/86740991

    三、拓扑排序的bfs模板

    有一个明确的思路:每一个顶点都有入度和出度,入度为0说明没有指向他的,那么就从他开始往下找。把这个入度为0的push进队列(还要注意保存入度为0的点),同时把与这个点相连的所有点的入度-1,然后再看看有没有入度为0的,有的话继续push,循环上面的操作,直到没有入度为0的点。
    看一下上面的图,如果从序号1开始的话:1入度为0,push进队列,顶点2的入度-1,所以顶点2push进队列,3和5的入度-1,3push进队列,5push进队列,顶点3进队列后,顶点4入度-1,顶点4push进队列,所以输出结果就是1 2 3 5 4

    #include <bits/stdc++.h>
    
    using namespace std;
    const int INF = 0x3f3f3f3f;
    
    
    //本代码功能:以bfs输出一个有向无环图DAG的拓扑序
    const int N = 1010;
    vector<int> edge[N];    //邻接表
    int ind[N];             //入度数组
    queue<int> q;          //队列
    
    int n;          //n个结点
    int m;          //m条边
    int main() {
        //读入,建图
        cin >> n >> m;
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            edge[x].push_back(y);
            ind[y]++;//维护入度
        }
        //入度为零的放入队列
        for (int i = 1; i <= n; i++) if (!ind[i]) q.push(i);
    
        //广度优先搜索DAG,就是拓扑排序的模板
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            //输出拓扑序
            cout << x << " ";
            for (int i = 0; i < edge[x].size(); i++) { //遍历所有出边
                int y = edge[x][i];                    //目标结点
                //对接点入度-1,抹去这条入边
                ind[y]--;
                //如果入度为0,则入队列,准备处理它
                if (!ind[y]) q.push(y);
            }
        }
        return 0;
    }
    /**
    测试数据
     5 4
    
     1 2
     2 3
     2 5
     3 4
    
    参考答案:
    1 2 5 3 4
     */
    
    

    四、拓扑排序的dfs模板

    DFS是从一个点不断往下递归,比如说从序号1往下递归,有箭头就一直往下进行,直到到了最后一个元素,就开始往栈里(当然也可以是vector之类的,只不过需要反向再输出)push元素。比如说上面的从序号1开始,到序号2,序号3,序号4,到尽头了,就把4push进栈中,3push进栈,这个时候由于5也是2的下一个元素,所以5push进栈中,2push进栈,1push进栈,然后输出就是1 2 5 3 4.
    当然这个递归的顺序是与你输入的顺序有关的,不过思路都是这样的,由起始点向下递归。

    #include <bits/stdc++.h>
    
    using namespace std;
    const int INF = 0x3f3f3f3f;
    
    //本代码功能:以dfs输出一个有向无环图DAG的拓扑序
    const int N = 1010;
    
    bool st[N];             //标识是不是已经使用过
    vector<int> edge[N];    //邻接表
    vector<int> res;        //拓扑序列
    
    /**
     * 功能:深度优先搜索,记录拓扑序
     * @param u
     */
    void dfs(int u) {
        //如果访问过了,则返回,不再重复访问
        if (st[u])return;
        //标识u结点已使用
        st[u] = true;
    
        //遍历每个出边,找到下一组结点
        for (int v:edge[u]) if (!st[v]) dfs(v);
    
        //这一层完毕才把它自己扔进去,最后扔等于最先输出,因为后面是倒序输出的
        res.push_back(u);
    }
    
    int n;          //n个结点
    int m;          //m条边
    int main() {
        //读入,建图
        cin >> n >> m;
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            edge[x].push_back(y);
        }
        //将所有结点进行深入优先搜索
        for (int i = 1; i <= n; i++) dfs(i);
    
        //输出,从后向前输出
        for (int i = res.size() - 1; i >= 0; i--)
            cout << res[i] << " ";
    }
    
    /**
    测试数据
     5 4
    
     1 2
     2 3
     2 5
     3 4
    
    参考答案:
    1 2 5 3 4
     */
    
    

    五、dfs判断是否有环

    #include <bits/stdc++.h>
    using namespace std;
    
    /**
     5 5
    
     1 2
     2 3
     2 5
     3 4
     3 1
    
    对比上个例子,添加了一条3->1的边,就成了有向有环图.
    本题,也就不能输出拓扑序了,因为有环图没有拓扑序,拓扑序是针对DAG的。可以判断是否有环。
     */
    
    //本代码功能:以dfs判断一个有向图SDG是否有环
    const int N = 1010;
    
    int st[N];          //标识是不是已经使用过
    vector<int> edge[N];//邻接表
    int n;              //n个结点
    int m;              //m个关系
    
    /**
     * 功能:深度优先搜索,判断以u开头的图中是否有环,有环:true,无环:false
      有向有环图dfs判断是否有环只需要把st[]的状态改一下,原本是两种状态,0和1,
      现在改成 0,1,-1
      0:代表未访问
     -1:代表访问完毕
      1:代表是这一阶段正在访问的(这一阶段指的是两个元素在同一个递归中)。
     */
    bool dfs(int u) {
        //标识u结点正在访问
        st[u] = 1;
    
        //遍历每个出边,找到下一组结点
        for (int v:edge[u]) {
            //如果遇到了正在访问的结点,那么说明有环
            if (st[v] == 1) return true;
            //如果v这个结点没有访问过,递归查找v结点是否在环中
            if (st[v] == 0 && dfs(v)) return true;
        }
    
        //标识u结点访问完毕
        st[u] = -1;
        return false;
    }
    
    int main() {
        //读入,建图
        cin >> n >> m;
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            edge[x].push_back(y);
        }
        //将所有未访问过的结点进行深入优先搜索判断是否有环
        for (int i = 1; i <= n; i++)
            if (!st[i] && dfs(i))//没有访问过,并且有环
                cout << "发现环!" << endl;
        return 0;
    }
    
    

    六、bfs判断是否有环

    #include <bits/stdc++.h>
    
    using namespace std;
    
    /**
     5 5
    
     1 2
     2 3
     2 5
     3 4
     3 1
    
    对比上个例子,添加了一条3->1的边,就成了有向有环图.
    本题,也就不能输出拓扑序了,因为有环图没有拓扑序,拓扑序是针对DAG的。可以判断是否有环。
     */
    
    //本代码功能:以bfs判断一个有向图SDG是否有环
    const int N = 1010;
    
    int st[N];          //标识是不是已经使用过
    vector<int> edge[N];//邻接表
    int ind[N];         //入度表
    int n;              //n个结点
    int m;              //m个关系
    queue<int> q;       //队列
    vector<int> ans;   //ans 为拓扑序列
    int main() {
        //读入,建图
        cin >> n >> m;
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            edge[x].push_back(y);
            ind[y]++;
        }
        for (int i = 1; i <= n; i++) if (ind[i] == 0) q.push(i);  //将入度为0的点入队列
    
        while (!q.empty()) {
            int p = q.front();
            q.pop(); // 选一个入度为0的点,出队列
            ans.push_back(p);
            for (int i = 0; i < edge[p].size(); i++) {
                int y = edge[p][i];
                ind[y]--;
                if (ind[y] == 0) q.push(y);
            }
        }
        if (ans.size() == n) {
            for (int i = 0; i < ans.size(); i++)
                printf("%d ", ans[i]);
            printf("
    ");
        } else printf("No Answer!
    ");   //  ans 中的长度与n不相等,就说明无拓扑序列
    
        return 0;
    }
    
    
  • 相关阅读:
    对于捐赠承诺和劳务捐赠,不予以确认,但应在会计报表附注中披露
    R语言代写线性混合效应模型Linear Mixed-Effects Models的部分折叠Gibbs采样
    matlab代写MCMC贝叶斯方法用于加筋复合板的冲击载荷识别
    R语言代写dplyr-高效的数据变换与整理工具
    GIS代写遥感数据可视化评估:印度河流域上部的积雪面积变化
    R语言代写向量自回归模型(VAR)及其实现
    r语言代写实现似然的I(2)协整VAR模型弱外生性推理
    python代写缺失值处理案例分析:泰坦尼克数据
    Python代写高性能计算库——Numba
    matlab递归神经网络RNN实现:桨距控制控制风力发电机组研究
  • 原文地址:https://www.cnblogs.com/littlehb/p/15125824.html
Copyright © 2011-2022 走看看