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得证。
三、算法实现
四、参考代码
一、算法的步骤及图解
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]; }
相关文章推荐
- Kosaraju算法解决强连通问题
- poj1904 - King's Quest 强连通解决二分匹配问题
- Android Studio把Eclipse android项目当作依赖库lib来使用的一些问题解决
- Spring 解决Quartz定时任务被触发两次的问题
- 解决springmvc+mybatis+mysql中文乱码问题【转】
- Nginx 之常见报错问题解决
- 装X利器,动态3D特效壁纸软件Wallpaper Engine的免费获取及安装问题解决
- CH37X 常见问题梳理与解决流程
- 运行DNW出现访问内存违例的问题解决
- 先解决温饱问题
- 解决javascript动态改变img的src属性图片不显示问题
- 解决jQuery 1.4 json问题
- 解决全站字符乱码(POST和GET中文编码问题)
- Android Studio 显示函数用法提示悬浮窗,解决fetching documentation问题
- vue-cli中解决css引用图片打包后找不到文件资源的问题
- 解决blacklist nouveau问题
- 解决redis desktop manager 链接redis服务器链接不上问题
- 前Citrix技术总监:虚拟化将解决云计算安全问题
- 解决docs.google.com打不开问题
- 十四周实验报告2:学会使用循环控制语句解决实际问题