something important
- 力求描述性語言關鍵,簡練,避免大段文字轟炸
- 部分內容來自網絡
零.強連通圖,強連通分量
- 強連通圖定義:在有向圖G中,如果任意兩個不同的頂點相互可達,則稱該有向圖是強連通的。
舉個例子:下圖有三個子圖(強連通分量):{1,4,5},{2,3},{6}
- 求強連通分量的作用:把有向圖中具有相同性質的點找出來(求強連通分量),縮點,建立縮圖,能夠方便地進行其它操作
一.floyd算法
算法思想:如果任意兩個不同的頂點相互可達,則這兩點在同一強連通分量
- 因為效率極低,不常使用,但理解極為簡單
//核心代碼
for(int k=1;k<=n;++k)//枚舉中間點
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(g[i][k]&&g[k][j]) g[i][j]=1;
for(int i=1;i<=n;++i)//計算連通分量數
{
if(!vis[i]) vis[i]=++col;
for(int j=1;j<=n;++j)
if(g[i][j]==g[j][i]) vis[j]=vis[i];//同屬一個連通分量
}
二.Tarjan算法
算法思想:
tarjan算法基於對圖深度優先搜索,用棧存儲整個強連通分量,將每一個強連通分量作為搜索樹上的一個子樹,而這個圖,就是一個完整的搜索樹。
DFN[ ]存儲點的搜索次序編號,LOW[ ]存儲強連通分量(對應搜索樹上的一個)子樹的根
判斷強連通分量:一個點出棧時 DFN==LOW
來看一個例子:
銜接:搜索到1,LOW[4]變為1
//核心代碼
void tarjan(int x)
{
dfn[x]=low[x]=++tot;//新進點的初始化。
stack[++index]=x;//進棧
visit[x]=1;//表示在棧里
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])//如果沒訪問過
{
darjan(v);//遞歸訪問
low[x]=min(low[x],low[v]);//比較誰是誰的父親/兒子,父親相當於強連通分量子樹最小根
}
else if(visit[v]) low[x]=min(low[x],dfn[v]);//如果訪問過,並且還在棧里。
}
if(low[x]==dfn[x])//是強連通分量子樹里的最小根
{
++num;//分量數加一
do{
printf("%d ",stack[index]);
visit[stack[index--]]=0;
}while(x!=stack[index+1]);
puts("");
}
}
三.Kosaraju算法
算法思想:
Kosaraju算法,基於兩次DFS
第一次DFS:對原圖進行深度優先遍歷,記錄每個頂點的離開時間。
第二次DFS:選擇具有最晚離開時間的頂點,對反向圖進行深度優先遍歷,並標記能夠遍歷到的頂點,這些頂點構成一個強連通分量
舉個栗子:
-
深搜順序:
-
對原圖進行深度優先遍歷后,頂點的離開時間分別為:
1離開時間為7, 4離開時間為6,
2離開時間為5, 3離開時間為4,
5離開時間為3, 6離開時間為2,
7離開時間為1。 -
則按頂點按離開時間從大到小排列的序列為:1、4、2、3、5、6、7,
-
按上述序列對反向圖進行深度優先遍歷,屬於同一次深度優先遍歷的頂點則屬於同一個強連
-
結果:{1,2},{3},{4},{5,6,7}
//核心代碼
void dfs1(int x)
{
vis[x]=1;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[i]) dfs1(v);
}
stack[++index]=x;
}
void dfs2(int x)
{
vis[x]=index;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v]) dfs2(i);
}
}
int main()
{
for(int i=1;i<=n;++i)
if(!vis[i]) dfs1(i);
memset(vis,0,sizeof(vis)); index=0;
for(int i=1;i>=1;--i)//出棧,連通分量染色
if(!vis[i]) ++index,dfs2(stack[i]);
}
四.總結與對比
Kosaraju算法的優勢:
該算法依次求出的強連通分量已經符合拓撲排序.
Tarjan算法的優勢:
相比Kosaraju算法擁有時間和空間上的巨大優勢.
使用范圍:
一般圖中選用Tarjan算法,涉及求圖的拓撲性質時則選用Kosaraju算法
圖片來自百度文庫