强联通分量与双连通分量

强联通分量

  1.概念

 在有向图G中,如果两点互相可达,则称这两个点强连通,如果G中任意两点互相可达,则称G是强连通图。

 定理: 1、一个有向图是强连通的,当且仅当G中有一个回路,它至少包含每个节点一次。

                     2、非强连通有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。

任意有向图都可以分解成若不相干的强连通分量,这就是强连通分量分解。把分解后的强连通分量缩成一个顶点,就得到了一个DAG(有向无环图)。

  2.Korasaju算法

强连通分量分解可以通过两次简单的DFS实现。第一次DFS时,选取任意顶点作为起点,遍历所有尚未访问过的顶点,并在回溯前给顶点标号(post order,后序遍历)。对剩余的未访问过的顶点,不断重复上述过程。

完成标号后,越接近图的尾部(搜索树的叶子),顶点的标号越小。第二次DFS时,先将所有边反向,然后以标号最大的顶点为起点进行DFS。这样DFS所遍历的顶点集合就构成了一个强连通分量。之后,只要还有尚未访问的顶点,就从中选取标号最大的顶点不断重复上述过程。

package 图论;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

public class 强联通分量_Korasaju {

	static int n, m;
	static ArrayList<Integer>[] list1;
	static ArrayList<Integer>[] list2;
	static ArrayList<Integer> vs = new ArrayList<Integer>();
	static boolean[] vis;
	static int[] collection;// 强联通分量集合,同一个集合数字相同

	static void init() {
		list1 = new ArrayList[n];
		for (int i = 0; i < n; i++) {
			list1[i] = new ArrayList<Integer>();
		}
		list2 = new ArrayList[n];
		for (int i = 0; i < n; i++) {
			list2[i] = new ArrayList<Integer>();
		}
		vis = new boolean[n];
		collection = new int[n];
	}

	static void dfs(int u) {
		vis[u] = true;
		for (int j = 0; j < list1[u].size(); j++) {
			int v = list1[u].get(j);
			if (!vis[v]) {
				dfs(v);
			}
		}
		// 标号
		vs.add(u);
	}

	//搜索反向图
	static void rdfs(int u, int k) {

		vis[u] = true;
		collection[u] = k;// 顶点u属于第k个强联通分量集合

		for (int j = 0; j < list2[u].size(); j++) {
			int v = list2[u].get(j);
			if (!vis[v]) {
				rdfs(v, k);
			}
		}
	}

	static void scc() {

		// 遍历每一个顶点
		for (int i = 0; i < n; i++) {
			if (!vis[i])
				dfs(i);
		}

		for (int i = 0; i < n; i++) {
			vis[i] = false;
		}

		int k = 0;
		for (int i = vs.size() - 1; i >= 0; i--) {
			int v = vs.get(i);
			if (!vis[v]) {
				rdfs(v, k++);
			}
		}
		System.out.println("强联通分量集合:");
		System.out.println(Arrays.toString(collection));
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		init();
		for (int i = 0; i < m; i++) {
			int u = sc.nextInt() - 1;
			int v = sc.nextInt() - 1;
			list1[u].add(v);
			list2[v].add(u);// 反向建图
		}
		scc();

	}

}
/*
12
16
12 11
11 8
11 10
8 10
10 9
9 8
9 7
7 6
5 7
6 5
6 4
6 3
4 3
2 3
3 2
4 1
*/

 

3.Tarjan算法

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

那么怎么判断呢?

强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。  

代码如下:

package 图论;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Stack;

public class 强联通分量_tarjan {

	static int n, m, cnt, scc_cnt;
	static ArrayList<Integer>[] list;
	static boolean[] vis;
	static int[] dfn, low, num;
	static Stack<Integer> stack = new Stack<Integer>();

	static void init() {
		list = new ArrayList[n];
		for (int i = 0; i < n; i++) {
			list[i] = new ArrayList<Integer>();
		}
		vis = new boolean[n];
		dfn = new int[n];
		low = new int[n];
		num = new int[n];
	}

	static void tarjan(int u) {

		vis[u] = true;
		dfn[u] = low[u] = ++cnt;
		stack.add(u);

		for (int i = 0; i < list[u].size(); i++) {
			int v = list[u].get(i);
			// 如果下一个节点没有访问过
			if (!vis[v]) {
				tarjan(v);
				low[u] = Math.min(low[u], low[v]);
				// 如果下一个节点已经被访问过了,且不属于任何一个连通分量
			} else if (stack.contains(v)) {
				// v是连通分量一个节点,统一low值
				low[u] = Math.min(low[u], dfn[v]);
			}
		}
		// 如果节点u是强连通分量的根
		// 满足强连通分量的要求,当前节点u为该强连通分量最早发现的节点
		if (dfn[u] == low[u]) {
			scc_cnt++;// 强联通分量个数
			while (true) {
				int v = stack.pop();// 将v退栈,为该强连通分量中一个顶点
				num[v] = scc_cnt;// 同一个连通分量的同一个数字
				if (u == v)
					break;
			}
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		init();
		for (int i = 0; i < m; i++) {
			int u = sc.nextInt() - 1;
			int v = sc.nextInt() - 1;
			list[u].add(v);
		}
		for (int i = 0; i < n; i++) {
			if (!vis[i]) {
				tarjan(i);
			}
		}
		System.out.println(Arrays.toString(num));
	}

}
/*
 * 6 8 1 2 1 3 2 4 3 4 3 5 4 1 4 6 5 6
 */

双连通分量

双连通与强连通,最本质的差别就是前者适用于无向图中,而后者适用于有向图。至于两者的概念是一样的,就是图中有a点、b点,从a点可到达b点,同时从b点可到达a点。(若是有向图必须延方向到达。)

其中双连通分量可细分为:点-双连通分量,边-双连通分量。所谓点-双连通分量是指在一个无向图中两点间至少有两条路径,且路径中(不算头尾)的点不同。不同的点-双连通分量最多有一个公共点,这个点必定是“割点”。

边-双连通分量是指在一个无向图中两点间至少有两条路径,且路径中的边不同。边-双连通分量中一定没有桥。

  • 点-双连通图:一个连通的无向图内部没有割点,那么该图是点-双连通图。
  •         注意:孤立点,以及两点一边这两种图都是点-双连通的,因为它们都是内部无割点。

  • 边-双连通图:一个连通的无向图内部没有桥,那么该图就是边-双连通图。
  •         注意:孤立点是边-双连通的,但是两点一边不是边-双连通的。

所以可以看出判断双连通分量的条件:

判断一个图是不是点-双连通的只要看图中是否有割点。(比寻找割点多了个栈而已)

判断一个图是不是边-双连通的只要看图中是否有桥。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值