有向图G = (V, E)的一个强连通分支就是一个最大的顶点集合C,对于C中的每一对顶点u和v,有u可达v和v可达u,即顶点u和v是互相可达的。
Tarjan算法基于对图深度优先搜索,每个强连通分量C对应深搜树中的一颗子树T(u),搜索时,把当前深搜树中未处理的节点加入一个顶点栈S,回溯时可以判断栈顶到根u为一个强连通分量C。
定义dfn[u]为顶点u搜索的次序编号(发现时间),low[u]为u或u的后代能追溯到的最早的栈中节点次序号。当dfn[u] = low[u]时,以u为根的搜索子树且在栈S中的所有节点是一个强连通分量。
分析tarjan(u)过程对顶点v的检查条件if v in S
设当前顶点u,探索边e = (u, v)时,对边e分类讨论
case #1: 是树边,则low[u] = min {low[u], low[v]}
case #2: 是反向边,则low[u] = min {low[u], dfn[v] }
case #3: 是交叉边
(a)如果v所属的强连通分量scc[v]未访问,则low[u] = min {low[u], dfn[v] }
(b)如果v所属的强连通分量scc[v]已访问,则不考虑顶点v
case #4: 是正向边,则low[u] <= dfn[u] < dfn[v], 所以不考虑顶点v
package graph;
import java.util.ArrayList;
import java.util.Stack;
public class AdjListDirectedGraph<V, E> {
private Vertex<V>[] adj;// 邻接表
private int n;// 顶点数目
private int time = 0;
private int sccNum;// 强连通分量数目
private int[] scc;// 顶点v的强连通分量编号
private int[] dfn;// dfs发现时间
private int[] low;// 可追溯的最小dfn顶点
private int[] parent;// 在深搜树中的父节点
private Stack<Integer> stack;// 顶点栈
@SuppressWarnings("unchecked")
AdjListDirectedGraph(int capacity) {
adj = new Vertex[capacity];
n = 0;
}
public void addVertex(int v, V value) {
adj[v] = new Vertex<>(value);
n++;
}
public void addEdge(int u, int v) {
addEdge(u, v, null);
}
public void addEdge(int u, int v, E value) {
Edge prev = null;//
for (Edge curr = adj[u].first; curr != null; curr = curr.next) {
if (v == curr.v) {
curr.value = value;
return;
} else if (v < curr.v) {
if (prev == null) {
adj[u].first = new Edge<E>(u, v, value, curr);
} else {
prev.next = new Edge<E>(u, v, value, curr);
}
return;
}
prev = curr;
}
// 插入到链表尾部
if (prev == null) {
adj[u].first = new Edge<E>(u, v, value, null);// 第一个边结点
} else {
prev.next = new Edge<E>(u, v, value, prev.next);// 插入到边链表的尾部
}
}
public void printAdjList() {
StringBuilder vs = new StringBuilder();
StringBuilder es = new StringBuilder();
boolean wrc = false;
vs.append("{");
es.append("{");
for (int i = 0; i < n; i++) {
vs.append(adj[i].value);
if (i < n - 1) {
vs.append(", ");
}
for (Edge p = adj[i].first; p != null; p = p.next) {
if (wrc)
es.append(", ");
es.append("(");
es.append(adj[i].value);
es.append(", ");
es.append(adj[p.v].value);
es.append(")");
wrc = true;
}
}
vs.append("}");
es.append("}");
System.out.println(vs);
System.out.println(es);
}
void initialize() {
time = 0;
sccNum = 0;
parent = new int[n];
dfn = new int[n];
low = new int[n];
scc = new int[n];
stack = new Stack<>();
for (int u = 0; u < n; u++) {
parent[u] = -1;
dfn[u] = 0;
low[u] = 0;
scc[u] = 0;
}
}
public int stronglyConnectedComponent() {
initialize();// 初始化搜索信息
for (int u = 0; u < n; u++) {
if (dfn[u] == 0) {
tarjan(u);// 从顶点u开始搜索
}
}
return sccNum;
}
@SuppressWarnings("unchecked")
void printConnectedComponent() {
ArrayList<Integer>[] components = new ArrayList[sccNum + 1];// [1..sccNum]
for (int i = 1; i <= sccNum; i++) {
components[i] = new ArrayList<>();// 第i个连通分量
}
for (int u = 0; u < adj.length; u++) {
components[scc[u]].add(u);
}
// 打印每个连通分量
for (int i = 1; i <= sccNum; i++) {
System.out.print("{");
for (int j = 0; j < components[i].size(); j++) {
int v = components[i].get(j);// 顶点v
System.out.print(adj[v].value);
if (j < components[i].size() - 1) {
System.out.print(", ");
}
}
System.out.print("}");
System.out.println();
}
}
/**
* tarjan算法计算顶点low值和强连通分量
*
* @param u
*/
void tarjan(int u) {
dfn[u] = ++time;
low[u] = dfn[u];
stack.push(u);// 顶点u进栈
for (Edge e = adj[u].first; e != null; e = e.next) {
int v = e.v;//
if (dfn[v] == 0) {// 顶点未被访问
parent[v] = u;
tarjan(v);// 从顶点v继续搜索
if (low[v] < low[u]) {// 顶点u的后代可追溯的最早栈中顶点
low[u] = low[v];
}
} else if (scc[v] == 0) {// 顶点v已进栈, 尚未出栈
//if v in stack S, then low[u] = min {low[u], dfn[v]}
//case #1: 是反向边,顶点v在栈中
//case #2: 是交叉边,如果v所属的强连通分量scc[v]未访问,则v在栈中
//case #3: 是交叉边,如果v所属的强连通分量scc[v]已访问,则v不在栈中
//case #4: 是前向边,无论v是否在栈中,low[u] <= dfn[u] < dfs[v], 所以不会更新low[u]
if (dfn[v] < low[u]) {// 顶点u自己可追溯的最早栈中顶点
low[u] = dfn[v];
}
}
}
if (low[u] == dfn[u]) {// 强连通分量C对应的深搜树根u
sccNum++;
int x;
while ((x = stack.pop()) != u) {
scc[x] = sccNum;
}
scc[x] = sccNum;// x = u
}
}
static class Edge<E> {
int u;//
int v;
E value;
Edge<E> next;// 顶点u相同的下一条边
Edge(int u, int v, E value, Edge<E> next) {
super();
this.u = u;
this.v = v;
this.value = value;
this.next = next;
}
@Override
public String toString() {
return "<" + u + ", " + v + ">";
}
}
static class Vertex<V> {
V value;
Edge first;// 第一条边
Vertex(V value) {
super();
this.value = value;
}
@Override
public String toString() {
return "" + value + "";
}
}
}
package graph;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestAdjListDirectedGraph {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();// 顶点数
int m = in.nextInt();// 边数
in.nextLine();
AdjListDirectedGraph<Character, Object> graph = new AdjListDirectedGraph<>(n);
for (int i = 0; i < n; i++) {
graph.addVertex(i, (char) ('a' + i));
}
Pattern pattern = Pattern.compile("<([a-z]), ([a-z])>");//
for (int i = 0; i < m; i++) {
String s = in.nextLine();
Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
// pair (u, v)
int u = matcher.group(1).charAt(0) - 'a';
int v = matcher.group(2).charAt(0) - 'a';
graph.addEdge(u, v);
}
}
graph.stronglyConnectedComponent();// 计算强连通分量
graph.printConnectedComponent();// 打印强连通分量
}
}
result:
{f}
{c}
{a, b, d, e}