有向图的强连通分量
在有向图中,u可达v不一定意味v可达到u,相互可达的节点则属于同一个强连通分量。
某节点的传递闭包为该节点所处的强连通分量和它所有后代所处的强连通分量的节点。
若有向图的所有节点同属于一个强连通分量,则称该有向图为强连通图。
在有向图中。若某子图中的任一对节点都互为可达,则该子图称为有向图的强连通分量。
计算有向图中强连通分量的方法例如以下:将有向图G中每条边的方向取反,得到图G的一个转置GT,G和GT中的强连通分量同样。将每一个强连通分量缩成一个节点,就能够得到一个无环有向图Gscc。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
1、Procedure Kosaraju(G);
{调用dfs(G)计算出每一个节点的f[u];
计算图G的转置GT;
调用dfs(GT),在主循环中依照f[u]递减的顺序依次对每一个未訪问点执行dfs过程,则得到的每棵dfs树恰好相应于一个强连通分量;
}
? ? ? ? ? ? ? ? ? ? ? ? ? ?
V |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
d |
1 |
17 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
19 |
21 |
23 |
20 |
f |
16 |
18 |
15 |
14 |
13 |
6 |
12 |
11 |
10 |
26 |
22 |
24 |
25 |
? ? ? ??
f |
26 |
25 |
24 |
22 |
18 |
16 |
15 |
14 |
13 |
12 |
11 |
10 |
6 |
v |
9 |
12 |
11 |
10 |
1 |
0 |
2 |
3 |
4 |
6 |
7 |
8 |
5 |
0 |
0 |
0 |
0 |
1 |
2 |
2 |
2 |
2 |
2 |
3 |
3 |
2 |
模板:
const maxn=100;
var ? ? ? ?//map[x,i] 记录与点x邻接的第i个点的编号 ? map[x,0]记录和x邻接的点的个数
? map,map1:array[1..maxn,0..maxn]of integer; ?
? visit:array[1..maxn]of boolean; ? //记录该点是否被遍历过
? list:array[1..maxn]of integer; ? ? ?//记录n个点的遍历次序
? n,m,pos,scc:integer; ? ? ? ? ? ? ? ? ?//pos记录进入list数组的点的个数 ?scc记录强连通分量的个数?
procedure init;
var i,x,y:integer;
begin
? readln(n,m);
? for i:=1 to m do
? begin
? ? readln(x,y);
? ? inc(map[x,0]);
? ? map[x,map[x,0]]:=y; ??
? ? inc(map1[y,0]);
? ? map1[y,map1[y,0]]:=x;?
end;
end;
procedure dfs(p:integer);
var i,j,k:integer;
begin
? visit[p]:=true; k:=map[p,0];
? for i:=1 to k do
? begin
? ? j:=map[p,i];
? ? if not visit[j] then dfs(j);
? end;
? inc(pos); ?list[pos]:=p;
end;
procedure dfs1(p:integer);
var i,j,k:integer;
begin
? visit[p]:=true;
? k:=map1[p,0];
? for i:=1 to k do
? begin
? ? j:=map1[p,i];
? ? if not visit[j] then dfs1(j);
? end;
end;
procedure kosaraju;
var i,j,k:integer;
begin
fillchar(visit,sizeof(visit),false);
? for i:=1 to n do if not visit[i] then dfs(i);?
? fillchar(visit,sizeof(visit),false); ?scc:=0;
? for i:=pos downto 1 do ?//每深搜完一次,表示找完一个强连通图,增加scc
? ? if not visit[list[i]] then begin dfs1(list[i]); inc(scc); end;
end;
begin
? init;
? pos:=0;
? kosaraju;
? writeln(scc);
end.
2、Tarjan算法
Tarjan算法是基于对图深度优先搜索的算法,每一个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点增加一个堆栈。回溯时能够推断栈顶到栈中的节点是否为一个强连通分量。
算法流程演示:
1.从节点1開始DFS。把遍历到的节点增加栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。
2.返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。
3.返回节点3,继续搜索到节点4。把4增加堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。
节点6已经出栈,(4,6)是横叉边。返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。
4.继续回到节点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)。
模板:
var
? a:array [1..1000,1..1000] of longint;
? low,dfn,c:array [1..1000] of longint;
? v,f,ff:array [1..1000] of boolean;
? i,j,m,n,x,y,d,ans:longint;
function min(x,y:longint):longint;
begin
? if x>y then
? exit(y);
? exit(x);
end;
procedure tarjan(x:longint);
var
? i:longint;
begin
? inc(d);
? low[x]:=d;
? dfn[x]:=d;
? f[x]:=true;
? for i:=1 to c[x] do
? begin
? ? if not v[a[x,i]] then
? ? begin
? ? ? v[a[x,i]]:=true;
? ? ? tarjan(a[x,i]);
? ? ? low[x]:=min(low[x],low[a[x,i]]);
? ? end else
? ? begin
? ? ? if f[a[x,i]] then
? ? ? low[x]:=min(low[x],dfn[a[x,i]]);
? ? end;
? end;
? if dfn[x]=low[x] then
? inc(ans);
end;
begin
? readln(n);
? for i:=1 to n do
? begin
? ? read(x);
? ? while x<>0 do
? ? begin
? ? ? inc(c[i]);
? ? ? a[i,c[i]]:=x;
? ? ? read(x);
? ? end;
? end;
? for i:=1 to n do
? if not v[i] then
? begin
? ? v[i]:=true;
? ? tarjan(i);
? end;
? writeln(ans);
end.
有几道习题:
1.http://blog.csdn.net/boyxiejunboy/article/details/46891399
2.http://blog.csdn.net/boyxiejunboy/article/details/46891417
3.http://blog.csdn.net/boyxiejunboy/article/details/46891815
具体详见:http://download.csdn.net/detail/boyxiejunboy/8908487(更加具体哦)