1. 程式人生 > 其它 >拓撲排序 專題

拓撲排序 專題

拓撲排序(\(Topological\) \(sorting\)

拓撲排序指的是有向無環圖(\(DAG\));

學過計算機網路的知道計算機網路中有一個拓撲結構;

下面就是一個拓撲結構;

那拓撲序就是,圖中任意一對頂點\(u\)\(v\),若邊\(<u,v>∈E(G)\),則\(u\)線上性序列中出現在\(v\)之前

我們可以發現 拓撲序不是唯一的

接下來,我們需要知道一個概念——

對於有向圖的某個結點來說,我們把指向它的邊的數量叫做入度

從它發出的邊的數量稱為出度,這個都很好理解吧;

如何獲得一個拓撲序:

我們先來找一個 最容易理解的例子,那就是食物鏈,對一個自然界的食物鏈來說,一定存在拓撲序;

第一:不存在環

第二:有向

接下來我們來看看如何獲得一個拓撲序,我們觀察最左面的結點,一定是沒有指向它的邊,也就是入度為零,最右邊的結點呢,是一定不存在它指向別人的邊的,如果有,那麼它就不是最右邊的點也就是出度為零;

那就是說,所有入度為零的點都可以作為起點,我們開一個數組記錄入度的值;

至於如何使用鄰接表存邊,這就不展開解釋了,可以參考這個:傳送門

其實,拓撲排序也是\(bfs\)的一個簡單應用,我們需要 藉助佇列 來實現;

首先,我們遍歷儲存入度的陣列,獲得可以作為起點的結點,將其加入佇列;

接下來就可以愉快的遍歷了,沒當我們遍歷到一個點的時候,我們讓它的入度--;

這樣做的 意義 就是,判斷指向這個點的邊是不是都遍歷過了,因為我們要保證拓撲序最重要的一個特點:\(<u,v>\)

的邊中,\(u\)一定在\(v\)的前面出現;

如果這個點所有的邊都遍歷過的話,是不是也就是說這個點已經沒有指向它的邊了,也就是說這個點可以作為一個起點了,那我們將它加入佇列;迴圈這個操作,知道佇列為空;

\(P4017\) 最大食物鏈計數 - 洛谷

最大食物鏈數量;最大指的是需要從一個入度為零的點開始到一個出度為零的點,這是一個完整的食物鏈,問我們給出的食物網中,食物鏈的數量
① 本題中,不僅需要 記錄一下入度 , 還要 記錄一下出度,這是因為我們要計算食物鏈的數量,食物鏈的最後一個結點,就是出度為零的點
② 食物鏈的數量,就是一個類似於 數字三角形 求值的\(dp\)問題了

#include <bits/stdc++.h>
using namespace std;

const int N = 5010, M = 500010;
const int INF = 0x3f3f3f3f, MOD = 80112002;

int in[N], out[N], f[N];
int n, m;

//鏈式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void topsort() {
    queue<int> q;

    for (int i = 1; i <= n; i++)
        if (!in[i]) {
            q.push(i);
            f[i] = 1;
        }

    while (q.size()) {
        int u = q.front();
        q.pop();
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            in[j]--;
            f[j] = (f[j] + f[u]) % MOD;
            if (in[j] == 0) q.push(j);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);

    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        in[b]++, out[a]++;
    }

    topsort();

    int res = 0;

    for (int i = 1; i <= n; i++)
        if (!out[i]) res = (res + f[i]) % MOD;

    printf("%d", res);
    return 0;
}

\(P1137\) 旅行計劃

這個題,我們根據題意是不是知道這個是一個\(DAG\),我們需要計算的是以城市 \(i\) 為終點最多能夠遊覽多少個城市;這個是不是也是在一個拓撲序上做一個簡單的\(dp\)就行了,我們記錄一下最大值即可;

#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 2 * N, INF = 0x3f3f3f3f;

int n, m;

//鏈式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int in[N], f[N];

void topsort() {
    queue<int> q;
    for (int i = 1; i <= n; i++)
        if (!in[i]) {
            q.push(i);
            f[i] = 1;
        }

    while (q.size()) {
        int u = q.front();
        q.pop();
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            f[j] = max(f[j], f[u] + 1);
            in[j]--;
            if (in[j] == 0) q.push(j);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);

    for (int i = 1; i <= m; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        in[b]++;
    }

    topsort();

    for (int i = 1; i <= n; i++) printf("%d\n", f[i]);

    return 0;
}