求強連通分量的tarjan算法Gabow算法

求強連通分量的tarjan算法

強連通分量:是有向圖中的概念,在一個圖的子圖中,任意兩個點相互可達,也就是存在互通的路徑,那麼這個子圖就是強連通分量。(如果一個有向圖的任意兩個點相互可達,那麼這個圖就稱爲強連通圖)。

如果u是某個強連通分量的根,那麼:

1u不存在路徑可以返回到它的祖先

2u的子樹也不存在路徑可以返回到u的祖先。

· 例如:

· 強連通分量。在一個非強連通圖中極大的強連通子圖就是該圖的強連通分量。比如圖中子圖{1,2,3,5}是一個強連通分量,子圖{4}是一個強連通分量。

tarjan算法的基礎是深度優先搜索,用兩個數組lowdfn,和一個棧。low數組是一個標記數組,記錄該點所在的強連通子圖所在搜索子樹的根節點的dfn值,dfn數組記錄搜索到該點的時間,也就是第幾個搜索這個點的。根據以下幾條規則,經過搜索遍歷該圖和對棧的操作,我們就可以得到該有向圖的強連通分量。

算法規則:

· 數組的初始化:當首次搜索到點p時,DfnLow數組的值都爲到該點的時間。

· 堆棧:每搜索到一個點,將它壓入棧頂。

· 當點p有與點p’相連時,如果此時(時間爲dfn[p]時)p’不在棧中,plow值爲兩點的low值中較小的一個。

· 當點p有與點p’相連時,如果此時(時間爲dfn[p]時)p’在棧中,plow值爲plow值和p’dfn值中較小的一個。

· 每當搜索到一個點經過以上操作後(也就是子樹已經全部遍歷)的low值等於dfn值,則將它以及在它之上的元素彈出棧。這些出棧的元素組成一個強連通分量。

· 繼續搜索(或許會更換搜索的起點,因爲整個有向圖可能分爲兩個不連通的部分),直到所有點被遍歷。

算法僞代碼:

tarjan(u)
{
  DFN[u]=Low[u]=++Index       // 爲節點u設定次序編號和Low初值
  Stack.push(u)                   // 將節點u壓入 棧中
  for each (u, v) in E              // 枚舉每一條邊
    if (dfn[v])          // 如果節點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是強連通分量的根

do{
      v = S.pop            // v退棧,爲該強連通分量中一個頂點
    }while(u == v);
}

演示算法流程;

從節點1開始DFS,把遍歷到的節點加入棧中。搜索到節點u=6DFN[6]=LOW[6],找到了一個強連通分量。退棧到u=v爲止,{6}爲一個強連通分量。

返回節點5,發現DFN[5]=LOW[5],退棧後{5}爲一個強連通分量。

返回節點3,繼續搜索到節點4,把4加入堆棧。發現節點4向節點1有後向邊,節點1還在棧中,所以LOW[4]=1。節點6已經出棧,(4,6)是橫叉邊,返回3(3,4)爲樹枝邊,所以LOW[3]=LOW[4]=1

繼續回到節點1,最後訪問節點2。訪問邊(2,4)4還在棧中,所以LOW[2]=DFN[4]=5。返回1後,發現DFN[1]=LOW[1],把棧中節點全部取出,組成一個連通分量{1,3,4,2}

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

可以發現,運行Tarjan算法的過程中,每個頂點都被訪問了一次,且只進出了一次堆棧,每條邊也只被訪問了一次,所以該算法的時間複雜度爲O(N+M)

此外,該Tarjan算法與求無向圖的雙連通分量(割點、橋)Tarjan算法也有着很深的聯繫。學習該Tarjan算法,也有助於深入理解求雙連通分量的Tarjan算法,兩者可以類比、組合理解。

應用例子:(OJ 1484 popular cows)

題意:有N只牛,輸入a,b的話,則說明b關注a,而且關注有傳遞性。例如c關注b,且b關注a,則c也關注a。題目問有多少隻奶牛能被其他所有的奶牛關注。

把題目的模型轉換:N個頂點的有向圖,有M條邊。求一共有多少個點,滿足這樣的條件:所有其它的點都可以到達這個點。這個點滿足條件的充要條件是:這個點是樹中唯一的出度爲0的點

先求強連通分量,然後可以把強連通分量縮成一個點,因爲,在強連通分量中的任意兩個點可以到達,所有的點具有相同的性質,即它們分別能到達的點集都是相同的,能夠到達它們的點集也是相同的。然後就重新構圖,縮點後的圖是沒有強連通分量的,否則,可將環上所有點也縮成一個點,與極大強聯通分量矛盾。所以只要找出度爲0的點,並且出度爲0的點只有1個 ,如果出度爲0的點有多個的話,問題則無解。

代碼:

#include<stdio.h>

#include<string.h>

#define adj 10010

#define edg 50010

struct node

{

int v;

int next;

};

node edge[edg];

node edge1[edg];

int low[adj],dfn[adj],Stack[adj];

int first[adj],first1[adj],fuck[adj];

bool ins[adj];

int n,m,temp,cnt,top,count;

int cnt_size[adj],belong[adj],outdegree[adj];

void creat(int u,int v)                

{

edge1[count].next=first1[u];

edge1[count].v=v;

first1[u]=count++;

}

void tarjan(int u)

{

int i,v;

dfn[u]=low[u]=++temp;

Stack[top++]=u;

ins[u]=true;

for(i=first[u];i!=-1;i=edge[i].next)

{

v=edge[i].v;

if(!dfn[v])

{

tarjan(v);

if(low[u]>low[v])

low[u]=low[v];

}

else if(ins[v])

{

if(low[u]>dfn[v])

low[u]=dfn[v];

}

}

if(dfn[u]==low[u])

{

int j;

do

{

top--;

j=Stack[top];

ins[j]=false;

cnt_size[cnt]++;

belong[j]=cnt;

}while(u!=j);

cnt++;

}

}

int main()

{

int i,j,k,v,sum,num;

int e=0;

scanf("%d%d",&n,&m);

    memset(first,-1,sizeof(first));

for(k=0;k<m;k++)                建立圖

{

scanf("%d%d",&i,&j);

edge[e].v=j;

edge[e].next=first[i];

first[i]=e;

e++;

}

    memset(dfn,0,sizeof(dfn));

memset(ins,false,sizeof(ins));

temp=cnt=top=0;

memset(cnt_size,0,sizeof(cnt_size));

memset(low,0,sizeof(low));

for(i=1;i<=n;i++)              求強連通分量

{

if(!dfn[i])

tarjan(i);

}

memset(first1,-1,sizeof(first1));

count=0;

for(i=1;i<=n;i++)              重新構造圖

{

for(j=first[i];j!=-1;j=edge[j].next)

{

v=edge[j].v;

if(belong[i]!=belong[v])

creat(belong[i],belong[v]);

}

}

memset(outdegree,0,sizeof(outdegree));

for(i=0;i<cnt;i++)                 求每個節點的出度

{

for(j=first1[i];j!=-1;j=edge1[j].next)

outdegree[i]++;

}

sum=num=0;

for(i=0;i<cnt;i++)

{

if(outdegree[i]==0)           求節點爲0的個數

{

sum=cnt_size[i];

num++;

}

}

if(num==1)

printf("%d\n",sum);

else

printf("0\n");

return 0;

}

 

Gabow算法

來自NOCOW
跳轉到: 導航, 搜索

[編輯] 求解有向圖強連通分量的Gabow算法

Gabow算法與Tarjan算法的核心思想實質上是相通的,就是利用強連通分量必定是DFS的一棵子樹這個重要性質,通過找出這個子樹的根來求解強分量.具體到實現是利用一個棧S來保存DFS遇到的所有樹邊的另一端頂點,在找出強分量子樹的根之後,彈出S中的頂點一一進行編號. 二者不同的是,Tarjan算法通過一個low數組來維護各個頂點能到達的最小前序編號,而Gabow算法通過維護另一個棧來取代low數組,將前序編號值更大的頂點都彈出,然後通過棧頂的那個頂點來判斷是否找到強分量子樹的根


int Gabow(Graph G) {
  // 初始DFS用到的全局變量
  S = StackInit(G->V);  // S用來保存所有結點
  P = StackInit(G->V);  // P用來維護路勁
  int v;
  for (v = 0; v < G->V; ++v)
    pre[v] = G->sc[v] = -1;
  cnt = id = 0;
  // DFS
  for (v = 0; v < G->V; ++v)
    if (pre[v] == -1)
      GabowDFS(G, v);
  // 釋放棧空間
  StackDestroy(S);
  StackDestroy(P);
  return id;  // 返回id的值,這恰好是強連通分量的個數
}
 
void GabowDFS(Graph G, int w) {
  Link t;
  int v;
  pre[w] = cnt++;  // 對前序編號編號
  StackPush(S, w);  // 講路徑上遇到的樹邊頂點入棧
  StackPush(P, w);
  for (t = G->adj[w]; t; t = t->next) {
    if (pre[v = t->v] == -1)                  // 如果當前頂點以前未遇到,則對其進行DFS
      GabowDFS(G, v);
    else if (G->sc[v] == - 1)                 // 否則如果當前頂點不屬於強分量
      while (pre[StackTop(P)] > pre[v])  // 就將路徑棧P中大於當前頂點pre值的頂點都彈出
        StackPop(P);
  }
  if (StackTop(P) == w) {  // 如果P棧頂元素等於w,則找到強分量的根,就是w
    StackPop(P);
    do {
      v = StackPop(S);  // 把S中的頂點彈出編號
      G->sc[v] = id;
    } while (v != w);
    ++id;
  }
}
發佈了99 篇原創文章 · 獲贊 3 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章