首页 > 代码库 > c2java 第7篇 图的连通分量,关节点和桥
c2java 第7篇 图的连通分量,关节点和桥
====
对于有向图,我们称其一个子图是强连通分量,是指任意两点u,v, 都有两条路径u到v和v到u。
对于连通无向图,我门称其一个子图是双连通分量,是指任意两点u,v,存在一个圈包含u,v。与无向图相关联的还有关节点x,是指去掉x,图不连通;桥(u,v)是指去掉这条边,图不连通。
求解算法的要义在于首先要理解:树边-前向边-后向边-交叉边
"Consider what happens when a depth-first search is performed on a directed
graph G. The set of edges which lead to a new vertex when traversed during the
search form a tree. The other edges fall into three classes. Some are edges running from ancestors to descendants in the tree. These edges may be ignored, because
they do not affect the strongly connected components of G. Some edges run from
descendants to ancestors in the tree; these we may call fronds as above. Other edges run from one subtree to another in the tree. These, we call cross-links."[1]
每个强连通分量为搜索树中的一棵子树。
定义:
o dfn[u]为节点u搜索的次序编号(时间戳).
o low[u]为 u所在强连通子图对应的搜索子树的根节点的dfn值(u或u的子树能够追溯到的最早的栈中节点的次序号).
o 为了处理有向图的交叉边,引入comp[u]表示u属于于哪个连通分量.
对于边(x,y):
o (dfn[y] < dfn[x]) 表示这条边是后向边或者交叉边。
o (0 == comp[y])表示还在栈里面,即这条边不可能是交叉边。
o (low[y] >= dfn[x]) 表明x搜索树子树的根,即无向图的关节点 ([1] LEMMA5)。
o (low[y] > dfn[x]) 表明这条边是无向图的桥。
讨论:
o 对于无向图讨论强连通没有意义,因为它总是强连通的;对于有向图讨论关节点和桥,结果全是乱的。
o 求得scc之后,把每个scc缩成一个点,加上原图的边,构成一个有向无环图dag。
我可以边dfs边构造dag:在 comp[y]不为零时记录dag的边, 在输出scc时增加一个顶点,并添上刚才记录的边。与此有关的一个专利[3], 要是Tarjan先生把他的dfs框架专利了,还容得尔曹唧唧歪歪.
o Q:把一个有向图变成强连通,至少需要加多少条边?
A: 在dag中统计入度为零的顶点数s1和出度为零的顶点数s2, 结果为max(s1,s2)。
证明:不妨设s1>s2, 两组顶点编号为I[1..s1], D[1..s2], 可以这样连:D[1] 连I[2], D[2] 连I[3], ..., D[s2] 连I[s2+1],然后
I[s2+1] 连I[s2+2],I[s2+2]连I[s2+3],..., I[s1]连I[1]构成一个大外环。
o Q:把一个无向图变成双连通,至少需要加多少条边?
A:bcc 缩点后形成一棵树,统计树的叶子数目为s,则结果为(s+1)/2。
证明[4]:最近公共祖先(LCA)最远的两个叶子连一条边,然后再找下一个LCA最远的两个叶子连一条边,..., 这样两两配对。
为什么要跟LCA扯上关系,因为这样才能两个半圈不会部分重复。
o 强连通分量的应用:
Q: POJ2762 对于一个很大的有向图的任意两点,是否可达,要求复杂度为O(N)。
A: scc + 缩点 + dfs。
Q: IOPC2006 给定一个有向图G(V,E),问最多能从G图中删去多少条边,且删了边之后图G的连通性不变。换一种描述:给定一个有向图G(V,E),我们可以改造这个图G中的连边得到新图G’,问图G’中至少要含有多少边,才能满足G’的连通性与图G一致。
A: scc + 缩点后,按逆拓扑顺序,加入u和u出发的边,检查这些新加入的边是否可删除:
对于<u,v>, 如果存在x,使得u,x有边,且x到v有路径,则<u,v>可删。[2]
/* history: 2014.04.25 StringBuffer out 改成 LinkedList<Integer> out 2014.04.29 add Tarjan */ import java.io.*; import java.util.*; public class Graph { int n; /*顶点数*/ int[][] adjList; int[] edgeCnt, edgeCap; void trimToSize() { int i; for(i = 0; i < n; ++i){ adjList[i] = Arrays.copyOf(adjList[i], edgeCnt[i]); } edgeCnt = null; edgeCap = null; } int addEdge(int v, int w) { if(edgeCnt[v] >= edgeCap[v]){ int newCap = edgeCap[v] + 10; adjList[v] = Arrays.copyOf(adjList[v], newCap); edgeCap[v] = newCap; } adjList[v][edgeCnt[v]++] = w; return 0; } int[] getNeighbors(int v) { return adjList[v]; } void dfs(int v, byte[] color, LinkedList<Integer> out) { int i, w = 0; int[] nei = getNeighbors(v); color[v] = 1; for(i = 0; i < nei.length; ++i){ w = nei[i]; if(0 == color[w])dfs(w, color, out); else if(1 == color[w]){/*a cycle is found.*/} } color[w] = 2; out.addFirst( v); //System.out.printf("v = %d out = %s %n", v, out); } void topoSort(byte[] color, LinkedList<Integer> out ) { int i; out.clear(); Arrays.fill(color, (byte)0); for(i = 0; i < n; ++i){ if(0 == color[i])dfs(i, color, out); } } void bfs(int v, byte[] color, LinkedList<Integer> out) { int i, w; int[] nei; LinkedList<Integer> qu = new LinkedList<Integer>(); out.clear(); Arrays.fill(color, (byte)0); qu.push(v); while(!qu.isEmpty()){ v = qu.pop(); out.addLast(v); color[v] = 1; /*visited*/ nei = getNeighbors(v); for(i = 0; i < nei.length; ++i){ w = nei[i]; if(0 == color[w])qu.push(w); } } } public Graph(int vertexNum, int edgeNum, Scanner in) throws IOException {/*假设输入顶点数,边数,之后是m对边(v,w)*/ int i, v, w; n = vertexNum; adjList = new int[n][0]; edgeCnt = new int[n]; edgeCap = new int[n]; for(i = 0; i < n; ++i){ edgeCnt[i] = 0; edgeCap[i] = 10; adjList[i] = new int[edgeCap[i]]; } for(i = 0; i < edgeNum; ++i){ v = in.nextInt(); // - 1; w = in.nextInt(); // - 1; addEdge(v, w); } trimToSize(); } } class Tarjan { Graph g; int n; int[] dfn; /*每个顶点的dfn值*/ int[] low;/*每个顶点的low值*/ int[] stk;/*存储分量顶点的栈*/ int stp; int index; /*搜索次序*/ int[] comp; /*标记每个顶点属于哪个连通分量*/ int compTag; int[] cut;/*标记每个顶点是否为割点*/ int son; /*根节点孩子个数,大于2则根节点为关节点*/ int beginVertex; public Tarjan(Graph gRef) {/*common context init*/ g = gRef; n = g.n; dfn = new int[n]; low = new int[n]; } public void SCC() {/*求有向图的强连通分量*/ int i; Arrays.fill(dfn, 0); Arrays.fill(low, 0); stk = new int[n]; stp = 0; comp = new int[n]; compTag = 0; index = 0; i = 0; //2; for(; i < n; ++i){ if(0 == dfn[i])strongConnect(i); } stk = null; comp = null; } void strongConnect(int x) { int[] adj; int i, y; stk[stp++] = x; dfn[x] = low[x] = ++index; adj = g.getNeighbors(x); for(i = 0; i < adj.length; ++i){ y = adj[i]; if(0 == dfn[y]){ strongConnect(y); low[x] = Math.min(low[x], low[y]); }else if((dfn[y] < dfn[x]) && (0 == comp[y])){ low[x] = Math.min(low[x], dfn[y]); } } if(low[x] == dfn[x]){ ++compTag; System.out.printf("SCC(%d): ", compTag); do{ y = stk[--stp]; comp[y] = compTag; System.out.printf("%d:(%d,%d), ", y, dfn[y], low[y]); }while(y != x); System.out.printf("%n"); } } public void BCC() {/*求无向图的双连通分量,*/ int i; Arrays.fill(dfn, 0); Arrays.fill(low, 0); stk = new int[4*n]; stp = 0; cut = new int[n]; beginVertex = 0; son = 0; index = 0; biConnect(beginVertex, -1); if(son > 1)cut[beginVertex] = 1; System.out.printf("Cut: "); for(i = 0; i < n; ++i)if(cut[i] > 0)System.out.printf("%d, ", i); System.out.printf("%n"); stk = null; cut = null; } void biConnect(int x, int father) { int i, y, x1, y1; int[] nei; dfn[x] = low[x] = ++index; nei = g.getNeighbors(x); for(i = 0; i < nei.length; ++i){ y = nei[i]; if(y == father)continue; if(0 == dfn[y]){ stk[stp++] = x; stk[stp++] = y; biConnect(y,x); low[x] = Math.min(low[x], low[y]); if(low[y] >= dfn[x]){ System.out.printf("BCC(%d,%d): ", x, y); do{ y1 = stk[--stp]; x1 = stk[--stp]; System.out.printf("<%d:(%d,%d),%d:(%d,%d)>, ", x1, dfn[x1], low[x1], y1, dfn[y1], low[y1]); }while(dfn[x1] >= dfn[y]); System.out.printf("%n"); } if(x == beginVertex)++son; if(x != beginVertex && low[y] >= dfn[x])cut[x] = 1; if(low[y] > dfn[x])System.out.printf("Bridge <%d %d> %n", x, y); }else if(dfn[y] < dfn[x]){ stk[stp++] = x; stk[stp++] = y; low[x] = Math.min(low[x], dfn[y]); } } } } class Test { public static void main (String[] arg) throws IOException { int n, m; byte[] color; /*for dfs: 0--white, 1--gray, 2--black*/ LinkedList<Integer> out = new LinkedList<Integer>(); Scanner in = new Scanner(System.in); n = in.nextInt(); m = in.nextInt(); color = new byte[n]; Graph g = new Graph(n, m, in); in.close(); in = null; Arrays.fill(color, (byte)0); g.dfs(0, color, out); System.out.println("dfs: " + out); g.bfs(0, color, out); System.out.println("bfs: " + out); g.topoSort(color, out); System.out.println("topoSort: " + out); color = null; out = null; Tarjan t = new Tarjan(g); t.SCC(); t.BCC(); } }
对于这个图的输出结果:
ludi@msys ~/java
$ cat in.txt
8 14
0 1
1 2 1 4 1 5
2 3 2 6
3 2 3 7
4 0 4 5
5 6
6 5 6 7
7 7
ludi@msys ~/java
$ javac -encoding UTF-8 Graph.java && cat in.txt | java Test
dfs: [0, 1, 4, 2, 6, 5, 3, 7]
bfs: [0, 1, 5, 6, 7, 4, 2, 3]
topoSort: [0, 1, 4, 2, 6, 5, 3, 7]
SCC(1): 7:(5,5),
SCC(2): 5:(7,6), 6:(6,6),
SCC(3): 3:(4,3), 2:(3,3),
SCC(4): 4:(8,1), 1:(2,1), 0:(1,1),
BCC(3,7): <3:(4,4),7:(5,5)>,
Bridge <3 7>
BCC(2,3): <2:(3,3),3:(4,4)>,
Bridge <2 3>
BCC(6,5): <6:(6,6),5:(7,7)>,
Bridge <6 5>
BCC(2,6): <6:(6,5),7:(5,5)>, <2:(3,3),6:(6,5)>,
Bridge <2 6>
BCC(1,2): <1:(2,2),2:(3,3)>,
Bridge <1 2>
BCC(0,1): <4:(8,1),5:(7,7)>, <4:(8,1),0:(1,1)>, <1:(2,1),4:(8,1)>, <0:(1,1),1:(2,1)>,
Cut: 1, 2, 3, 6,
对这个无向图的输出结果:
5 10
0 1 0 4
1 0 1 2
2 1 2 3 2 4
3 2
4 0 4 2
$ javac -encoding UTF-8 Graph.java && cat in.txt | java Test
dfs: [0, 1, 2, 4, 3]
bfs: [0, 4, 2, 3, 1, 1]
topoSort: [0, 1, 2, 4, 3]
SCC(1): 4:(5,1), 3:(4,3), 2:(3,1), 1:(2,1), 0:(1,1),
BCC(2,3): <2:(3,3),3:(4,4)>,
Bridge <2 3>
BCC(0,1): <4:(5,1),0:(1,1)>, <2:(3,1),4:(5,1)>, <1:(2,1),2:(3,1)>, <0:(1,1),1:(2,1)>,
Cut: 2,
[1] R. Tarjan Depth-First Search and Linear Graph Algorithms.
[2] 浅谈强连通分量与拓扑排序的应用 http://3y.uu456.com/bp-01a8sfefsef7ba0d4a733b3e-1.html.
[3] 标识强连通分量的入口和出口的技术 http://www.google.com/patents/CN102279738A?cl=zh
[4] 点连通度与边连通度 https://www.byvoid.com/blog/biconnect/
c2java 第7篇 图的连通分量,关节点和桥