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();

	}
}

对于这个图的输出结果:

[email protected] ~/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

[email protected] ~/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/

时间: 2024-12-14 00:05:19

c2java 第7篇 图的连通分量,关节点和桥的相关文章

图论篇6——割点(关节点)

引入 连通图 在一个**无向图**$G$中,若从顶点$i$ 到顶点$j$有路径相连,则称 $i$和$j$是连通的.如果图中任意两点都是连通的,那么图被称作连通图.如果$G$是有向图,则称为强连通图(注意:需要双向都有路径).如果是单向连通,则称$G$为单向连通图. 割点(关节点) 在无向连通图$G=(V,E)$中: 若对于$x\in V$, 从图中删去节点$x$以及所有与$x$关联的边之后, $G$分裂成两个或两个以上不相连的子图, 则称$x$为$G$的割点. 简而言之, 割点是无向连通图中的一

图的连通分量(利用邻接表存储信息)

用vector实现邻接表 vector <int> G[100]; //表示有100个顶点的图的邻接表 G[u].push_back(v); //从顶点u 向顶点v 画边,即在相当于创建一个二维数组G[100][i] //搜索与顶点u 相邻的顶点v for( int i = 0; i < G[u].size(); i++) { int v = G[u][i]; ....... } 邻接表表示法的优点 只需与边数成正比的内存空间 邻接表表示法的缺点 (1)设u 的相邻顶点数量为n,那么在调

【关节点+桥】关节点和桥模板 Tarjan

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 1e5, M = 1e5; struct Edge { int v, next, idx; Edge(){} Edge(int _v, int _next, int _idx): v(_v), next(_next), idx(_idx){} }e[M]; int dfn[N], dee

【连通图】无向图关节点和桥 Tarjan

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 1e5, M = 1e5; struct Edge { int v, next, idx; Edge(){} Edge(int _v, int _next, int _idx): v(_v), next(_next), idx(_idx){} }e[M]; int low[N], dfn

无向图的割顶和桥

割顶: 关键点,删掉这个点后,图的连通分量 + 1: 桥: 在割顶的基础上,发现删除 (u,v) 这条边,图就变成非连通的了. 如何找出所有割顶和桥: 时间戳: 在无向图的基础上,DFS建树的过程中,各点进栈和出栈的时间 dfs_clock,进栈的时间 pre[],出栈的时间 post[] 在DFS程序中的体现就是: void previst(int u) { pre[u]= ++dfs_clock; } void postvist(int u) { post[u] = ++dfs_clock;

7-8-无向图的关节点-图-第7章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第7章  图 - 无向图的关节点 ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Status.h.ALGraph.c        相关测试数据下载  链接? 数

【转】BYV--有向图强连通分量的Tarjan算法

转自beyond the void 的博客: https://www.byvoid.com/zhs/blog/scc-tarjan 注:红色为标注部分 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶

图论算法 有图有代码 万字总结 向前辈致敬

图的定义 背景知识 看到这篇博客相信一开始映入读者眼帘的就是下面这幅图了,这就是传说中的七桥问题(哥尼斯堡桥问题).在哥尼斯堡,普雷格尔河环绕着奈佛夫岛(图中的A岛).这条河将陆地分成了下面4个区域,该处还有着7座连接这些陆地的桥梁. 问题是如何从某地出发,依次沿着各个桥,必须经过每座桥且每座桥只能经过1次,最终回到原地. 不知道这个问题且好奇的童鞋现在肯定在忙活着找出来这道题的结果了. 是伟大的数学家欧拉(Leonhard Euler)在1736年首次使用图的方法解决了该问题. 欧拉将上面的模

关节点

关节点概念 删去顶点v以及v关联的各边之后,将图的一个连通分量分割成两个或两个以上的连通分量,则称顶点v为图的一个关节点(articulationpoint) 图经深度优先遍历,深度优先前序遍历给节点v编号(编号的值用visited[v]表示),对于祖先k,子节点w,一定有visited[k]<visited[w]. 节点一共有三种,分别对它们进行关节点分析: 1.根节点,若生成树的根节点有两颗或两颗以上的子树,则根节点为关节点. 子树之间除了根节点,必然没有关联. 因为如果还有其他关联,深度优