对于求有向图的强连通分量 , 我常用的一般只有这三种算法Kosaraju、Tarjan、Garbow,而这三种算法 ,
Tarjan和Garbow算法的思想是一样的 , 只不过是实现的方法不一样 , 下面就逐一分析这三个算法:
1、Kosaraju
Kosaraju算法主要是利用有向图中原图和其反图之间的关系
, 对于一个原图中的强连通分量 , 其在反图中还是强连通分量 , 原图中存在u 到 v的边 , 在反图中就只存在 v 到 u的边 ,
而不存在u 到 v的边。 根据这个关系 , 我们先用原图进行dfs搜索 , 得到了一个森林(树) ,
我们再按照原图中得到的dfs搜索序列 , 在其反图中进行dfs搜索 , 每次搜索能遍历到的顶点就是一个强连通分量。
代码:
//Kosaraju算法
int dfsone(int cur)
{
temp[cur] =
true;
for(int i =
head[cur] ; i != -1; i = edge[i].next)
{
if(!temp[edge[i].t]) dfsone(edge[i].t);
}
num[++sig] =
cur;
return
0;
}
int dfstwo(int cur , int sig)
{
temp[cur] =
true;
scc[cur] =
sig;
for(int i =
head2[cur] ; i != -1; i = edge[i].next2)
{
if(!temp(edge[i].f))
dfstwo(edge[i].f , sig);
}
return
0;
}
int Kosaraju()
{
int sig =
0;
memset(temp
, 0 , sizeof(temp));
for(int i =
1; i <=n ; i++)
{
if(!temp[i])
dfsone(i ,
sig);
}
sig =
0;
memset(temp
, 0 , sizeof(temp));
for(int i =
n; i > 0; i--)
{
if(!temp[num[i]])
{
dfstwo(num[i] , sig++);
}
}
return
sig;
}
2、Tarjan算法
Tarjan是利用跟求割点和桥的算法的思想差不多 ,
都是利用dfs + 时间戳 。
如果对原图进行dfs搜索 , 那么我们有强连通分量的定义可知
, 任何一个强连通分量是原图的dfs搜索的子树 , 那么我们只要确定了强连通分量在dfs序列中的根 , 我们就能确定这个强连通分量 。
在进行dfs时 , 我们用两个数组tem 、 low来记录每个点的时间戳 , low记录每个点的访问时间 ,
tem记录所有子孙的最小low值 ,tem的初始值和low一样 。 所以在dfs搜索的回溯过程中 , 如果low[v] ==
tem[v] , 我们我们就能确定一个强连通分量。
代码:
//tarjan算法
void dfs(int u)
{
node.push(u);
low[u] =
tem[u] = ++dfs_clock;
int i;
for(i =
head[i] ; i != -1; i = edge[i].next)
{
v =
edge[i].to;
if(!pre[v])
{
pre[v] =
1;
int lowv = =
dfs(v);
tem[u] =
min(lowv , tem[u]);
}
else
if(!sccno[i]) tem[u] = min(tem[u] ,
tem[v]);//tem是记录每个点的子孙所能到达的low最小的点
}
if(low[u] ==
tem[u])
{
scc_clock++;
do
{
int x =
node.top(); node.pop();
sccno[x] =
scc_clock;
}while(x !=
u)
}
}
3、Garbow算法
Garbow算法和Tarjan算法的另一种实现Tarjan算法是用了tem数组来求强连通分量的根 ,
而Carbow算法是利用一个stack来求强连通分量的根。
代码:
//carbow算法
void dfs(int u)
{
node1.push(u) , node2.push(u);
low[u] =
++dfs_clock;
int i ,
v;
for(i =
head[i] ; i != -1; i = edge[i].next)
{
v =
edge[i].to;
if(!pre[v])
{
pre[v] =
1;
dfs(v);
}
else
if(!sccno[v]) //这里就表示已经存在一个环了 , node2就把这个环中除点v之外所有点都删除
{
while(low[node2.top()] > low[v])
node2.pop();
}
}
if(node2.top() == u)
{
scc_clock++;
node2.pop();
do
{
x =
node1.top();
node1.pop();
sccno[x] =
scc_clock;
}while(x !=
u)
}
}
三种算法的比较:
这三种算法由于都是用的邻接表 , 因此时间复杂度都为:O(n+m) , 但是Carbow算法的常数最小 , Tarjan次之 , 并且Carbow算法的是实现非常精妙。
1、Kosaraju
代码:
//Kosaraju算法
int dfsone(int cur)
{
}
int dfstwo(int cur , int sig)
{
}
int Kosaraju()
{
}
2、Tarjan算法
代码:
//tarjan算法
void dfs(int u)
{
}
3、Garbow算法
代码:
//carbow算法
void dfs(int u)
{
}
三种算法的比较:
这三种算法由于都是用的邻接表 , 因此时间复杂度都为:O(n+m) , 但是Carbow算法的常数最小 , Tarjan次之 , 并且Carbow算法的是实现非常精妙。