您的位置:首页 > 其它

Kosaraju算法解决强连通问题

2012-04-14 15:28 134 查看
今天花了一上午时间实现这个算法,一开始一直没有理解,加上好久不动算法,结果效率很低。下面将从中学到的一些东西总结在这里吧。

一、算法的步骤及图解

Kosaraju 算法也许最容易理解的一个算法是Kosaraju 于80 年代提
出的,它执行两次DFS。第一次DFS 得到了关于各个SCC 拓扑顺序的
有关信息,而第二次DFS 按照这个拓扑顺序的逆序进行DFS,从而把
每个SCC 分开。算法步骤如下:
第1 步:对有向图进行DFS,记录下顶点变黑的时间A[i];遍历结
果构建一个森林W1,我们对森林中的每棵树做②、③步操作;
第2 步:改变图G 的每一条边的方向Gr;
第3 步:按上次DFS 顶点变黑的时间A[i]由大到小的顺序对Gr 进
行DFS,遍历的结果构成森林W2,W2 中每棵树的结点构成了有向图
的一个SCC。
算法的时间复杂度为O(n+m),下面我们来举例说明为什么经过上
面的操作可以得到连通分支:



第1 步:对于有向图G 一次DFS 遍历得到的森林:

有上面可得到如下结论:
●在每个生成树中,根的时间戳最大;
●根可以到达该树的任何一个结点(其他结点不一定能到达根);
●每棵子树的根的时间戳都大于其后代结点;他也可以到达它的所有
后代;
第2 步:现在我们对图进行反向,然后按时间戳由大到小进行dfs
得到森林如下:



通俗讲,这个原理就是:第一次dfs 的生成树中若存在回边,那么
边反方向后仍然可以相互到达,于是就是一个连通分量。

二、算法的证明

假设在后来转置后的图中从x dfs到了 y处,说明存在路径x->y。因为这是在转置图中,所以说明原图中存在路径y->x

然后另外一个信息就是x的序号在y之后。这有两种可能:

1、以y为根先DFS出了一棵搜索树(可以认为是整个搜索树的一棵子树),但是这棵子树里不包含x,并且此时x还未被dfs到。(利用反证法,如果这棵子树里包含了x,那么x的序号会在y之前)

2、y是x扩展出来的搜索树中的一个结点。

综合两个条件,综合两个条件取交。那么上面两种可能中的第一种不成立。因为存在路径y->x,所以如果x未被dfs到,一定会被y为根的搜索树包含的。于是只剩下第二种可能,那么第二种情况表明存在路径x->y。所以x,y可以互相到达。至此证明了该算法求出的都是强连通分量。命题1得证。

三、算法实现

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;

public class Kosaraju {
public static HashMap<Integer, Node> G = new HashMap<Integer, Node>(),
GR = new HashMap<Integer, Node>();
public static int cnt = 0, cntR = 0;
public static int[] pre = new int[1000000], postR = new int[1000000],
sc = new int[1000000];

public static void main(String args[]) {
initializeGraph();
for (int i = 1; i < 1000000; i++) {
pre[i] = -1;
postR[i] = -1;
sc[i] = -1;
}
System.out.println("Begin DFS 1");
for (int i = 1; i < 1000000; i++) {
if (pre[i] == -1) {
dfsR(i);
}
}
cnt = 0;
System.out.println("Begin DFS 2");
for (int i = 900000; i >= 0; i--) {
if (sc[postR[i]] == -1) {
if (G.get(i+1) != null){
dfsSc(postR[i]);
cnt++;
}
// System.out.println("DFS2   " + cnt);
}
}
System.out.println(cnt);
}

public static void initializeGraph() {
try {
FileReader fr = new FileReader(
"C:\\Users\\waruzhi\\Desktop\\SCC.txt");
BufferedReader br = new BufferedReader(fr);
String tmp;
int nodeA, nodeB;
while ((tmp = br.readLine()) != null) {
nodeA = Integer.parseInt(tmp.substring(0, tmp.indexOf(" ")));
nodeB = Integer.parseInt(tmp.substring(tmp.indexOf(" ") + 1,
tmp.length() - 1));
Node tA = G.get(nodeA), tB = G.get(nodeB), tAR = GR.get(nodeA), tBR = GR
.get(nodeB);
if (tA == null) {
Node newNode = new Node(nodeA);
G.put(nodeA, newNode);
tA = newNode;
newNode = new Node(nodeA);
GR.put(nodeA, newNode);
tAR = newNode;
}
if (tB == null) {
Node newNode = new Node(nodeB);
G.put(nodeB, newNode);
tB = newNode;
newNode = new Node(nodeB);
GR.put(nodeB, newNode);
tBR = newNode;
}
if (tA != tB) {
tA.out.add(tB);
tBR.out.add(tAR);
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public static void dfsR(int num) {
Node tmp = GR.get(num);
if (tmp != null) {
pre[num] = cnt++;
// System.out.println("DFS1:   " + num + ";cnt = " + cnt);
for (Node aNode : tmp.out) {
if (pre[aNode.num] == -1) {
dfsR(aNode.num);
}
}
}
postR[cntR++] = num;
}

public static void dfsSc(int num) {
Node tmp = G.get(num);
if (tmp != null) {
sc[num] = cnt;
for (Node aNode : tmp.out) {
if (sc[aNode.num] == -1) {
dfsSc(aNode.num);
}
}
}
}
}


四、参考代码

// Kosaraju算法邻接矩阵实现

static int cnt, cntR, pre[MAXV], postR[MAXV];
int Kosaraju(Graph G) {
int v;
// 初始化全局变量
cnt = cntR = 0;
for (v = 0; v < G->V; ++v)
pre[v] = postR[v] = -1;
// 第一次DFS,计算逆图的后序编号
for (v = 0; v < G->V; ++v)
if (pre[v] == -1)
dfsPostR(G, v);
cnt = 0;
for (v = 0; v < G->V; ++v)
G->sc[v] = -1;  // G->sv[v]表示顶点v的强连通分量编号
// 第二次DFS,强连通分量编号
for (v = G->V - 1; v >= 0; --v) {
// 注意搜索的顶点顺序是逆图后序编号的逆序
if (G->sc[postR[v]] == -1) {
dfsSC(G, postR[v]);
++cnt;  // 对一棵树编号之后计数器值加1
}
}
return cnt;  // 返回强连通分量的个数
}

void dfsPostR(Graph G, int v) {
// 对逆图后序编号
int t;
pre[v] = cnt++;
for (t = 0; t < G->V; ++t)
if (G->adj[t][v] == 1)  // 注意!!!邻接矩阵引用逆图,因此是G->adj[t][v]
if (pre[t] == -1)
dfsPostR(G, t);
postR[cntR++] = v;  // 后序编号,注意是计数器做数组下标
}

void dfsSC(Graph G, int v) {
int t;
G->sc[v] = cnt; // 计数器作为编号
for (t = 0; t < G->V; ++t)
if (G->adj[v][t] == 1)
if (G->sc[t] == -1)
dfsSC(G, t);
}

int GraphSC(Graph G, int s, int t) {
// 比较顶点的强连通分量编号即可判断是否强连通
return G->sc[s] == G->sc[t];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: