天天看點

算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練

算法期中1007. 怪獸訓練

有向圖強連通分量

在有向圖G中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。

下圖中,子圖{1,2,3,4}為一個強連通分量,因為頂點1,2,3,4兩兩可達。{5},{6}也分别是兩個強連通分量。

算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練

Tarjan算法

Tarjan算法是基于對圖深度優先搜尋的算法,每個強連通分量為搜尋樹中的一棵子樹。搜尋時,把目前搜尋樹中未處理的節點加入一個堆棧,回溯時可以判斷棧頂到棧中的節點是否為一個強連通分量。

定義DFN(u)為節點u搜尋的次序編号(時間戳),Low(u)為u或u的子樹能夠追溯到的最早的棧中節點的次序号。

算法僞代碼

tarjan(u) {
    DFN[u]=Low[u]=++Index     // 為節點u設定次序編号和Low初值
    Stack.push(u)                     // 将節點u壓入棧中
    for each (u, v) in E               // 枚舉每一條邊
        if (v is not visted)          // 如果節點v未被通路過
            tarjan(v)              // 繼續向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in S)            // 如果節點v還在棧内
            Low[u] = min(Low[u], DFN[v])
    if (DFN[u] == Low[u])        // 如果節點u是強連通分量的根
       repeat
           v = S.pop                  // 将v退棧,為該強連通分量中一個頂點
           print v
       until (u== v)
}
           

算法流程示範

從節點1開始DFS,把周遊到的節點加入棧中。搜尋到節點u=6時,DFN[6]=LOW[6],找到了一個強連通分量。退棧到u=v為止,{6}為一個強連通分量。
算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練
傳回節點5,發現DFN[5]=LOW[5],退棧後{5}為一個強連通分量。
算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練
傳回節點3,繼續搜尋到節點4,把4加入堆棧。發現節點4向節點1有後向邊,節點1還在棧中,是以LOW[4]=1。節點6已經出棧,(4,6)是橫叉邊,傳回3,(3,4)為樹枝邊,是以LOW[3]=LOW[4]=1。
算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練
繼續回到節點1,最後通路節點2。通路邊(2,4),4還在棧中,是以LOW[2]=DFN[4]=5。傳回1後,發現DFN[1]=LOW[1],把棧中節點全部取出,組成一個連通分量{1,3,4,2}。
算法期中1007. Tarjan算法計算有向圖強連通分量算法期中1007. 怪獸訓練

至此,算法結束。經過該算法,求出了圖中全部的三個強連通分量{1,3,4,2},{5},{6}。

可以發現,運作Tarjan算法的過程中,每個頂點都被通路了一次,且隻進出了一次堆棧,每條邊也隻被通路了一次,是以該算法的時間複雜度為O(N+M)。

題目

貝爺的人生樂趣之一就是約戰馬會長. 他知道馬會長喜歡和怪獸對決,于是他訓練了N隻怪獸,并對怪獸用0到N-1的整數進行編号. 貝爺訓練怪獸的方式是讓它們一對一互毆. 兩隻怪獸互毆會發生以下三種可能的結果:

1) 什麼事也沒發生

2) 第一隻怪獸永遠消失

3) 第二隻怪獸永遠消失

怪獸們經過了曠日持久的互毆. 貝爺不知道哪些怪獸進行了互毆也不知道它們互毆的順序,但他确信無論經過多少次互毆,總有一些怪獸能存活下來,他将要派這些怪獸去和馬會長對決. 現在他想知道至少有多少隻怪獸能存活下來,你能幫他算出來嗎?

請實作下面Solution類中的minLeftMonsters函數,完成上述功能.

參數G: N*N(1 <= N <= 50)字元矩陣,G[i][j]表示怪獸i和怪獸j互毆會發生的結果. 字元‘+’代表怪獸i會消失,’-’代表怪獸j會消失,數字’0’則代表什麼都沒發生. 輸入保證G[i][i]一定是’0’,而且G[i][j]和G[j][i]一定相反(’-’和’+’互為相反,’0’和自身相反).

傳回值:怪獸存活的最少數目.

例1:

G =

0+-

-0+

+-0

傳回1.

例2:

000

000

000

傳回3.

解題分析

這道題是讓我們計算在經過多次無序的互毆之後、怪物存活的最少數量。那我們來怎麼思考這個問題呢?

其實我們可以把怪物抽象成一個個節點,然後把怪獸消失的動作抽象成一條條有向路徑,這樣的話,我們就可以得到一個有向圖,表示怪物之間互毆的結果。

很明顯,這樣的話,如果若幹隻怪物是互斥的,即若幹個節點構成一個環,那麼這幾隻怪物最後存活的數量最少為1,剛好是環的個數,也即計算強連通分量的個數。

這樣我們就可以通過上面提到的Tarjan算法的思想來解決這個問題,在此就不再贅述~

源代碼

class Solution {
public:
    int STACK[], top;          //Tarjan 算法中的棧 
    bool InStack[];             //檢查是否在棧中 
    int DFN[];                  //深度優先搜尋通路次序 
    int Low[];                  //能追溯到的最早的次序 
    int ComponetNumber;        //有向圖強連通分量個數 
    int Index;                 //索引号 
    vector <int> Edge[];        //鄰接表表示 
    vector <int> Component[];   //獲得強連通分量結果

    int minLeftMonsters(vector<vector<char>> G) {
        top = ComponetNumber = Index = ;
        memset(STACK, -, sizeof(STACK));
        memset(InStack, , sizeof(InStack));
        memset(DFN, -, sizeof(DFN));
        memset(Low, -, sizeof(Low));
        for (int i = ; i < ; i++) {
            while (!Edge[i].empty()) {
                Edge[i].pop_back();
            }
            while (!Component[i].empty()) {
                Component[i].pop_back();
            }
        }
        for (int i = ; i < G.size(); i++) {
            for (int j = ; j < G[i].size(); j++) {
                if (G[i][j] == '+') {
                    Edge[i].push_back(j);
                }
            }
        }
        for (int i = ; i < G.size(); i++) {
            if (DFN[i] == -) {
                Tarjan(i);
            }
        }
        return ComponetNumber;
    }

    void Tarjan(int i) {
        int j;
        DFN[i] = Low[i] = Index++;
        InStack[i] = true;
        STACK[++top] = i;
        for (int edge = ; edge < Edge[i].size(); edge++) {
            j = Edge[i][edge];
            if (DFN[j] == -) {
                Tarjan(j);
                Low[i] = min(Low[i], Low[j]);
            }
            else if (InStack[j]) {
                Low[i] = min(Low[i], DFN[j]);
            }
        }
        if (DFN[i] == Low[i]) {
            ComponetNumber++;
            do {
                j = STACK[top--];
                InStack[j] = false;
                Component[ComponetNumber].push_back(j);
            } while (j != i);
        }
    }
};
           

以上是我對這道問題的一些想法,有問題還請在評論區讨論留言~

繼續閱讀