1. 程式人生 > 其它 >拓撲排序 (DFS和BFS及判斷是否有環)

拓撲排序 (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;
}

四、拓撲排序的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
 */