一:无向图的强连通分量算法
向图的连通分量就都是强连通分量。无向图的强连通分量就是用DFS算法顺序遍历邻接表时顺道干点小动作,写下代码更直观一些:
#define maxN 1024
int marked[maxN];//用于记录某个点是否被访问过,0为没有被临幸过,1为被临幸过
int id[maxN];//记录每个点所属的连通分量
int count;//记录连通分量总数目
void cc(graph *g){
int i;
memset(marked,0,sizeof(marked));
memset(id,0,sizeof(id));
count=0;
for(i=0;i<g->V;i++){//之所以这里用循环就是因为g指向的无向图可能不是一个连通图,而是由多个连同分量组成
if(!marked[i]){dfs(g,i); count++;}
}
}
void dfs(graph *g,int v){
graphNode *t;
marked[v]=1;
id[v]=count;
t=g->adjlist[v].next;//t指向v的邻接点
while(t){
if(!marked[t->key]){dfs(g,t->key);}
这里是重点,就是你发现v到t->key有路径就把它算到跟自己在一个连通分量里了,这里有一个隐性前提,就是你提前知道t->key一定可以到v,所以你发现v可以到t->key的时候,你毫不犹豫把它算为跟自己一伙儿的了。
二:有向图的强连通分量的三种算法
Kosaraju Algorithm
如果不仅需要统计强连通分量的个数,还要将强连通分量缩点,则需要用到今天介绍的Kosaraju Algorithm。它的具体步骤如下:
- 对原图 G 进行DFS并将出栈顺序进行逆序,得到的顺序就是拓扑序列。
- 将原图的每一条边反向,得到反图 G′ 。
- 按照第一步生成的拓扑序列的顺序再对反图 G′ 进行DFS染色,染成同色的就是一个强连通分量。(注意:这个dfs的访问顺序是按照拓扑排序的顺序,想想为啥捏么?因为加入有a--->b--->c,先访问a,此时我们假定a可以到b,当遍历1到n时,如果发现b能到a,则a和b为连通分量)
#include <iostream>
#include <cstring>
#include <stack>
using namespace std;
const int MAX = 10240;
int N, M, nCnt = 0;
int pMap[MAX][MAX], pColor[MAX];
stack<int> S; // 储存拓扑序列
void dfs1(int x); // 原图DFS
void dfs2(int x); // 反图DFS
void Kosaraju();
int main()
{
cin >> N >> M;
memset(pMap, 0, sizeof(pMap));
for(int i = 1; i <= M; i++)
{
int s, e;
cin >> s >> e;
pMap[s][e] = 1; // 有向图
}
Kosaraju();
return 0;
}
void Kosaraju()
{
memset(pColor, 0, sizeof(pColor));
for(int i = 1; i <= N; i++) // DFS原图求出拓扑序列
{
if(!pColor[i])
{ dfs1(i); }
}
memset(pColor, 0, sizeof(pColor));
while(!S.empty()) // 按照拓扑序列DFS反图
{
int x = S.top(); S.pop();
if(!pColor[x])
{
nCnt++; // 找到一个强连通分量
dfs2(x);
}
}
cout << "The number of SCC is " << nCnt << endl;
}
void dfs1(int x)
{
pColor[x] = 1; // 染色
for(int i = 1; i <= N; i++)
{
if(pMap[x][i] == 1 && !pColor[i])
{ dfs1(i); }
}
S.push(x); // 加入拓扑序列
}
void dfs2(int x)
{
pColor[x] = nCnt; // 属于第几个强连通分量
for(int i = 1; i <= N; i++)
{
if(pMap[i][x] == 1 && !pColor[i]) // 原邻接矩阵的对称矩阵为反图
{ dfs2(i); }
}
}
Tarjan算法
此算法以一个有向图作为输入,并按照所在的强连通分量给出其顶点集的一个划分。图中的每个结点只在一个强连通分量中出现,即使是在有些结点单独构成一个强连通分量的情况下(比如图中出现了树形结构或孤立结点)。
算法的基本思想如下:任选一结点开始进行深度优先搜索(若深度优先搜索结束后仍有未访问的结点,则再从中任选一点再次进行)。搜索过程中已访问的结点不再访问。搜索树的若干子树构成了图的强连通分量。
结点按照被访问的顺序存入栈中。从搜索树的子树返回至一个结点时,检查该结点是否是某一强连通分量的根结点(见下)并将其从栈中删除。如果某结点是强连通分量的根,则在它之前出栈且还不属于其他强连通分量的结点构成了该结点所在的强连通分量。
根结点的性质[编辑]
算法的关键在于如何判定某结点是否是强连通分量的根。注意“强连通分量的根”这一说法仅针对此算法,事实上强连通分量是没有特定的“根”的。在这里根结点指深度优先搜索时强连通分量中首个被访问的结点。
为找到根结点,我们给每个结点v一个深度优先搜索标号v.index,表示它是第几个被访问的结点。此外,每个结点v还有一个值v.lowlink,表示从v出发经有向边可到达的所有结点中最小的index,v.lowlink为v或v的子树能够追溯到的最早的栈中节点的次序号。显然v.lowlink总是不大于v.index,且当从v出发经有向边不能到达其他结点时,这两个值相等。v.lowlink在深度优先搜索的过程中求得,v是强连通分量的根当且仅当v.lowlink = v.index。
algorithm tarjan is
input: 图 G = (V, E)
output: 以所在的强连通分量划分的顶点集
index := 0
S := empty // 置栈为空
for each v in V do
if (v.index is undefined)
strongconnect(v)
end if
function strongconnect(v)
// 将未使用的最小index值作为结点v的index
v.index := index
v.lowlink := index
index := index + 1
S.push(v)
// 考虑v的后继结点
for each (v, w) in E do
if (w.index is undefined) then
// 后继结点w未访问,递归调用
strongconnect(w)
v.lowlink := min(v.lowlink, w.lowlink)
else if (w is in S) then
// w已在栈S中,亦即在当前强连通分量中
v.lowlink := min(v.lowlink, w.index)
end if
// 若v是根则出栈,并求得一个强连通分量
if (v.lowlink = v.index) then
start a new strongly connected component
repeat
w := S.pop()
add w to current strongly connected component
until (w = v)
output the current strongly connected component
end if
end function
第三种方法待续。。。。。