强联通分量
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点。(若是有向图必须延方向到达。)
其中双连通分量可细分为:点-双连通分量,边-双连通分量。所谓点-双连通分量是指在一个无向图中两点间至少有两条路径,且路径中(不算头尾)的点不同。不同的点-双连通分量最多有一个公共点,这个点必定是“割点”。
边-双连通分量是指在一个无向图中两点间至少有两条路径,且路径中的边不同。边-双连通分量中一定没有桥。
- 点-双连通图:一个连通的无向图内部没有割点,那么该图是点-双连通图。
-
注意:孤立点,以及两点一边这两种图都是点-双连通的,因为它们都是内部无割点。
- 边-双连通图:一个连通的无向图内部没有桥,那么该图就是边-双连通图。
-
注意:孤立点是边-双连通的,但是两点一边不是边-双连通的。
所以可以看出判断双连通分量的条件:
判断一个图是不是点-双连通的只要看图中是否有割点。(比寻找割点多了个栈而已)
判断一个图是不是边-双连通的只要看图中是否有桥。