1. 程式人生 > >超詳細Tarjan演算法總結,求強連通分量,割點,割邊,有重邊的割邊

超詳細Tarjan演算法總結,求強連通分量,割點,割邊,有重邊的割邊

Tarjan是一個人,他一身中發明了很多演算法,就這幾個演算法最為出名。

1、求有向圖的強連通分量,那麼什麼是強連通分量呢,就是一個頂點集合,任意兩個頂點間都可以互相到達。一個頂點也是強聯通分量如果圖中任意兩點可以互相到達,則此圖強連通。下圖中頂點{1,0,2}屬於一個強聯通分量,{3},{4}也屬於;

TARJAN是基於dfs演算法的基礎上,所以也會得到一顆搜尋樹。如右邊圖


那麼他是怎麼執行的呢,首先大家看著兩個陣列 

dfn[i],和low[i];一開始dfn【i】=low【i】=cnt++;

dfn[i]表示第i個節點深搜的順序。例如根據右圖深搜的順序可以得到dfn【1】=1;dfn【0】=2;dfn【2】=3;依次類推下去;

low【i】表示i節點能夠追溯到那個強聯通分量最先訪問的那個點的dfn值;因為他們是強聯通分量所以他們一定會有環。既low【2】=dfn【1】=1;low【0】=low【2】=dfn【1】=1;  low【3】=dfn【3】=4;的值就不會改變因為她下面的點沒有邊能夠連線到他的祖先上去,也是因為沒有環。low【4】得值也不會改變;

大家此時有沒有發現一個規律 既 每個強聯通分量的第一個訪問的那個次序low【i】都會等於dfn【i】;這裡有三個想等所以他們有三個強聯通分量。

具體操作大家看下面配合程式碼的具體演示吧;

程式碼:

void Tarjan(int u)//此程式碼僅供參考
{
    vis[u]=1;
    low[u]=dfn[u]=cnt++;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(vis[v]==0){Tarjan(v);
                      low[u]=min(low[u],low[v]);}
         else  if(vis[v]==1)  low【u】=min(low【u】,dfn【v】);
} if(dfn[u]==low[u]) { sig++; } }
程式碼模擬一遍哈!!! 請大家認真仔細的用筆寫,看哈;首先執行tarjan(u);此時u等於1;執行low【1】=dfn【1】=1;將這個頂點標記為已經訪問過

vis【1】=1;

節點0有沒有被訪問過,節點0沒有被訪問過往下面深搜此時搜到0,執行tarjan(0);執行low【0】=dfn【0】=2;繼續判斷

繼續深搜執行low【2】=dfn【2】=3;關鍵的來了哈,此時節點2繼續往下面深搜 搜到了節點1;但是1已經被訪問過了,就不繼續深搜了。執行low【2】=min(low【2】,dfn【1】);所以low【3】此時被賦值為1;然後第三層tarjan的迴圈語句結束了。執行判斷dfn【3】是不是等於low【3】;這裡是不等於所以計數器不增加;現在返回到第二層tarjan此時剛執行完tarjan【2】;接著更新low【0】則執行low【0】=min(low【0】,low【2】);現在繼續迴圈v=3;判斷v有沒有被訪問過這裡是沒有被訪問過執行tarjan(3);接著low【3】=dfn【3】=4;然後進入迴圈體 v=4;然後節點4沒有被訪問過執行tarjan(4);low【4】=dfn【4】=5;接著進入迴圈體,因為下面沒有節點與4相連則退出迴圈體;則執行判斷low【4】==dfn【4】此時他們想等計數器加1;接著返回到tarja(3);執行low【3】=min(low【3】,low【4】)因為節點4沒有往上面去的邊,所以low【4】還是等於4並且大於low【3】;此時的low【3】也是同理不變;此時這個迴圈體也執行完了此時判斷low【3】==dfn【3】這裡是想等的所以計數器增加1;

此時函式返回到tarjan(0);在執行low【0】=min(low【3】);此時low【0】=1小於low【3】;則不更新low【0】;現在迴圈體也執行完了判斷low【0】==dfn【0】;這裡是不相等的;計數器不加;返回到tarjan(1);此時他的迴圈體執行完了判斷low【1】==dfn【1】;這裡是想等計數器加1;最後求得強聯通分量的個數為3;然後返回到主函式;以上就是tarjan演算法的執行過程;

那麼大家有沒有想過萬一圖不連通呢;

所以我們在外面的這樣處理  在外面加入一個迴圈;

程式碼;

#include<stdio.h>//此程式碼僅供參考,用於求一個圖存在多少個強連通分量
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define maxn 1000000
vector<int >mp[maxn];
int ans[maxn];
int vis[maxn];
int dfn[maxn];
int low[maxn];
int n,m,tt,cnt,sig;
void init()
{
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)mp[i].clear();
}
void Tarjan(int u)
{
    vis[u]=1;
    low[u]=dfn[u]=cnt++;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(vis[v]==0)Tarjan(v);
        if(vis[v]==1)low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u])
    {
        sig++;
    }
}
void Slove()
{
    tt=-1;cnt=1;sig=0;
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0)
        {
            Tarjan(i);
        }
    }
    printf("%d\n",sig);
}
int main()
{
    while(~scanf("%d",&n))
    {
        if(n==0)break;
        scanf("%d",&m);
        init();
        for(int i=0;i<m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            mp[x].push_back(y);
        }
        Slove();
    }
}

這裡求強聯通分量就將講到這裡了;

推薦大家入門題目;hdu迷宮城堡;傳送門http://blog.csdn.net/lw277232240/article/details/72123936

下面我們求割點;

割點什麼是割點呢;就是在一個連通圖中刪除一個點就導致真個圖不連通了,那麼這個點就叫做割點;下圖實邊為dfs搜尋的邊,虛線邊為回邊,既可以訪問父親的祖先的邊;

例如下圖的A就是一個割點;

該演算法是R.Tarjan發明的。觀察DFS搜尋樹,我們可以發現有兩類節點可以成為割點:

  1. 對根節點u,若其有兩棵或兩棵以上的子樹,則該根結點u為割點;
  2. 對非葉子節點u(非根節點),若其子樹的節點均沒有指向u的祖先節點的回邊,說明刪除u之後,根結點與u的子樹的節點不再連通;則節點u為割點。

對於根結點,顯然很好處理;但是對於非葉子節點,怎麼去判斷有沒有回邊是一個值得深思的問題。

我們用dfn[u]記錄節點u在DFS過程中被遍歷到的次序號,low[u]記錄節點u或u的子樹通過非父子邊追溯到最早的祖先節點(即DFS次序號最小),這裡的dfn  low 跟上面的功能是一樣的。,那麼low[u]的計算過程如下:

low[u]={min{low[u],low[v]}min{low[u],dfn[v]}(u,v)(u,v)vulow[u]={min{low[u], low[v]}(u,v)為樹邊min{low[u], dfn[v]}(u,v)為回邊且v不為u的父親節點

下表給出圖(a)對應的dfn與low陣列值。

i 0 1 2 3 4 5 6 7 8 9 10 11 12
vertex A B C D E F G H I J K L M
dfn[i] 1 5 12 10 11 13 8 6 9 4 7 2 3
low[i] 1 1 1 5 5 1 5 5 8 2 5 1 1

對於情況2,當(u,v)為樹邊且low[v] >= dfn[u]時,節點u才為割點。該式子的含義:以節點v為根的子樹所能追溯到最早的祖先節點要麼為v要麼為u。


程式碼:

void tarjan(int u,int pre)
{
    dfn[u]=low[u]=cnt++;
    vis[u]=1;
    for(int i=0;i<G[u].size();i++)
    {  int a=G[u][i];
        if(a==pre) continue;
        if(!vis[a])
        {tarjan(a,u);
            low[u]=min(low[u],low[a]);
            if(u==pre) {gen++;}//當他為跟的時候
            else if(low[a]>=dfn[u]) cut[u]++;//這裡是不是跟 切low[a]>=dfn[u],那麼這個點u就是割點,這裡是統計割點的個數就執行cut【u】++
        }
        else {
               low[u]=min(low[u],dfn[a]);
             }
    }
   if(pre==u) cut[pre]=gen-1;

}