如何深入淺出的理解 Kosaraju

前言

今天想起來Kosaraju,網上關於這個算法的介紹比較少。(畢竟Tarjan太強了)。但是Tarjan和Kosaraju的複雜度都是O(V+E)O(V+E)的,Kosaraju的常數要大一點。(網上有的博客說kosaraju會卡爆棧,個人感覺不會,退化成鏈的情況Tarjan和Kosaraju都會一搜到底)。

那爲什麼Kosaraju常數大還要學它呢,用Tarjan不好嗎?

因爲它簡單、好理解啊。畢竟Tarjan難理解是出了名的。

正題

一些必要概念

網上介紹各種概念五花八門,不夠深入淺出。首先要理解這幾個概念:

  • 前序序列(從一點開始遍歷,結點進入的序列)
  • 後序序列(從一點開始遍歷,結點退出的序列)
  • 逆後序序列(就是後序序列的逆序,沒什麼高深的意思,所以百度搜不到
  • GG
  • 反圖GG'(將圖GG的各個邊反過來重新建圖,出邊改入邊)
  • 強連通分量SCC(移步百度)

求前序序列和後序序列的代碼(如果上面不理解,看看代碼就懂了)

int n, dcnt, fcnt, c[N], d[N], vis[N], f[N];
vector<int> G[N];
void dfs(int x) {
    d[x] = ++dcnt;
    vis[x] = 1;
    for (auto y : G[x]) {
        if (!vis[y]) dfs(y);
    }
    v[x].n = ++fcnt;
}
void solve() {
    dcnt = fcnt = 0;
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) dfs(i);
    }
}

Kosaraju如和實現

兩遍DFS:

  • 第一遍,求出圖G的逆後序序列。

  • 第二遍,根據逆後序序列,在反圖GG'上進行DFS,每次能dfs點就在一個強連通分量裏。

代碼:

int n, c[N], dfn[N], vis[N], dcnt, scnt;
vector<int> G1[N], G2[N];  // G1 原圖,G2 反向圖
void dfs1(int x) {         // 求後序序列
    vis[x] = 1;
    for (auto y : G1[x]) {
        if (!vis[y]) dfs1(y);
    }
    dfn[++dcnt] = x;
}
void dfs2(int x) {
    c[x] = scnt;
    for (auto y : G2[x]) {
        if (!c[y]) dfs2(y);
    }
}
void kosaraju() {
    dcnt = scnt = 0;
    memset(c, 0, sizeof(c));
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) dfs1(i);
    }
    // 反過來遍歷dfn就是逆後序序列
    for (int i = n; i >= 1; i--) {
        if (!c[dfn[i]]) ++scnt, dfs2(dfs[i]);
    }
}

Why?如何理解

詳細的數學證明請參考《算法導論》,這裏給出如何一種正確理解的方法。

首先要知道:

  • 原圖和反圖具有相同的SCC(強連通分量)。

那爲什麼要求後序序列或者逆後序序列呢?

實際上是在求一個拓撲排序,但是帶環圖沒有拓撲排序的概念,這個逆後序序列就差不多是原圖縮點後的拓撲排序序列。

在這裏插入圖片描述
如圖所示,從1號節點開始逆後序序列爲:8、1、3、2、7、4、5、6

縮點後就是:

在這裏插入圖片描述

此圖的拓撲排序爲:S3(8)、S2(1、3、2)、S1(7、4、5、6)

然後如果在反圖上按照逆拓撲序列遍歷的話每次只會遍歷到一個SCC。這樣,這個算法就可以正常求出所有強連通分量了

問題

爲什麼要在反圖上做逆後序序列?在原圖上做後序序列不可以嗎?

是這樣的,上述給的例子,在原圖上做後序序列是完全可以的。但只要稍加改動,原圖的逆序序列有很多種(並不唯一)。比如:

  • 逆後序序列1:8、1、3、2、7、4、5、6
  • 逆後序序列2:8、1、3、7、4、5、6、2

序列1、2都是合法的逆序序列。上面介紹用的逆後序序列1。

他們的後序序列:

  • 後序序列1:6、5、4、7、231、8

  • 後序序列2:2、6、5、4、7、31、8

這裏大家需要手動模擬一下(很簡單),如果在原圖上採用後序序列2,會得到錯誤的答案。但是在反圖上採用逆後序序列2,答案仍舊正確。

因此我們只能在反圖上做逆後序序列。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章