概念
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
Tarjan算法
Tanjan算法可以看作是DFS算法+并查集,使用DFS算法将每一个强连通分量最为搜索树上的一个子树。
参数的含义如下:
- dfn[i]:节点i点次序编号(时间戳),简单来说就是第几个被搜索到的节点,其中dfn[i]=-1,表示节点i并没有被搜索。
- low[i]:表示能到达这个节点的最小编号,如果dfn[i]=low[i]表示节点i自己能够到达自己。
- time:时间戳,也就是节点编号。
- stack:为了存储在整个强连通分量,使用堆栈。
伪代码
tarjan(u){//当前节点
dfn[u] = low[u] == time++ //为节点设定编号
stack.push(u) //将节点压入栈中
for each (u,v) in E //枚举有节点u出度的每一条边
if(v is not visted) //如果节点v未被访问
tarjan(v) //继续向下查找
low[u] = min(low[u],low[v]) //更新low[],注意这里是low[v]
else if (v in stack) //如果节点v被访问过,且还在栈中
low[u] = min(low[u],dfn[v]) //注意这里是dfn[v]
if(dfn[u] == low[u]) //这表明节点u是强连通分量的根
repear v = stack.pop //将v退栈,为该连通分量中一个顶点
print v
until (u==v)
}
Tarjan算法的运行过程:(这段引用:https://blog.csdn.net/jeryjeryjery/article/details/52829142)
1.首先就是按照深度优先搜索算法搜索的次序对图中所有的节点进行搜索。
2.在搜索过程中,对于任意节点u和与其相连的节点v,根据节点v是否在栈中来进行不同的操作:
*节点v不在栈中,即节点v还没有被访问过,则继续对v进行深度搜索。
*节点v已经在栈中,即已经被访问过,则判断节点v的DFN值和节点u的low值的大小来更新节点u的low值。如果节点v的 DFN值要小于节点u的low值,根据low值的定义(能够回溯到的最早的已经在栈中的节点),我们需要用DFN值来更新u 的low值。
3.在回溯过程中,对于任意节点u与其子节点v(其实不能算是子节点,只是在深度遍历的过程中,v是在u之后紧挨着u的节点)的 low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路 径,所以v能够到的节点u也一定能够到。
4.对于一个连通图,我们很容易想到,在该连通图中有且仅有一个节点u的DFN值和low值相等。该节点一定是在深度遍历的过 程中,该连通图中第一个被访问过的节点,因为它的DFN值和low值最小,不会被该连通图中的其他节点所影响。下面我们证 明为什么仅有一个节点的DFN和low值相等。假设有两个节点的DFN值和low值相等,由于这两个节点的DFN值一定不相同 (DFN值的定义就是深度遍历时被访问的先后
次序),所以两个的low值也绝对不相等。由于位于同一个连通图中,所以两个节点必定相互可达,那么两者的low值一定会 被另外一个所影响(要看谁的low值更小),所以不可能存在两对DFN值和low值相等的节点。
所以我们在回溯的过程中就能够通过判断节点的low值和DFN值是否相等来判断是否已经找到一个子连通图。由于该连通图中 的DFN值和low值相等的节点是该连通图中第一个被访问到的节点,又根据栈的特性,则该节点在最里面。所以能够通过不停 的弹栈,直到弹出该DFN值和low值相同的节点来弹出该连通图中所有的节点。
代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class Tarjan {
private int numOfNode;
private List<ArrayList<Integer>> graph;// 图
private List<ArrayList<Integer>> result;// 保存极大强连通图
private boolean[] inStack;// 节点是否在栈内
private Stack<Integer> stack;
private int[] dfn;
private int[] low;
private int time;
public Tarjan(List<ArrayList<Integer>> graph, int numOfNode) {
this.graph = graph;
this.numOfNode = numOfNode;
this.inStack = new boolean[numOfNode];
this.stack = new Stack<Integer>();
dfn = new int[numOfNode];
low = new int[numOfNode];
Arrays.fill(dfn, -1);// 将dfn所有元素都置为1,其中dfn[i]=-1代表这个点没有被访问过
Arrays.fill(low, -1);
result = new ArrayList<ArrayList<Integer>>();
}
public void tarjan(int current) {
dfn[current] = low[current] = time++;
inStack[current] = true;
stack.push(current);
for (int i = 0; i < graph.get(current).size(); i++) {
int next = graph.get(current).get(i);
if (dfn[next] == -1) {
tarjan(next);
low[current] = Math.min(low[current], low[next]);
} else if (inStack[next]) {
low[current] = Math.min(low[current], dfn[next]);
}
}
if (low[current] == dfn[current]) {
ArrayList<Integer> temp = new ArrayList<Integer>();
int j = -1;
while (current != j) {
j = stack.pop();
inStack[j] = false;
temp.add(j);
}
result.add(temp);
}
}
public List<ArrayList<Integer>> run() {
for (int i = 0; i < numOfNode; i++) {
if (dfn[i] == -1)
tarjan(i);
}
return result;
}
public static void main(String[] args) {
// 创建图
int numOfNode = 6;
List<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>();
for (int i = 0; i < numOfNode; i++) {
graph.add(new ArrayList<Integer>());
}
graph.get(0).add(1);
graph.get(0).add(2);
graph.get(1).add(3);
graph.get(2).add(3);
graph.get(2).add(4);
graph.get(3).add(0);
graph.get(3).add(5);
graph.get(4).add(5);
Tarjan t = new Tarjan(graph, numOfNode);
List<ArrayList<Integer>> result = t.run();
// 打印结果
for (int i = 0; i < result.size(); i++) {
for (int j = 0; j < result.get(i).size(); j++) {
System.out.print(result.get(i).get(j) + " ");
}
System.out.println();
}
}
}