可达性问题
- 单点可达性:回答“是否存在一条从起点s到给定节点v的有向路径?”等类似问题。
- 多点可达性:回答“是否存在一条从集合中任意顶点到给定节点v的有向路径?”等类似问题。
- 顶点对的可达性:回答“是否存在一条从一个给定节点v到给定节点w的有向路径?”等类似问题。
单点可达性和多点可达性:
使用深度优先遍历很容易实现。
算法实现:
public class DirectedDFS{
private boolean[] marked;
//单点可达性
public DirectedDFS(Digraph G,int s){
mark = new boolean[G.V()];
dfs(G,s);
}
//多点可达性
public DirectedDFS(Digraph G, Iterable<Integer> sources){
mark = new boolean[G.V()];
for(int s : sources)
if(!marked[s]) dfs(G,s);
}
//深度优先遍历算法
private void dfs(Graph G,int v) {
marked[v] = true;
for(int w: G.adj(v))
if(!marked[w]) dfs(G,w);
}
public boolean marked(int v){ return marked[v]; }
}
顶点对之间的可达性:
在无向图中,这个问题很好解决,等价于连通性问题。无向图中需要线性时间的预处理就能达到常数时间的查询操作。但在有向图中,该问题目前还达不到这样的效率。
有向图G的传递闭包是由相同的一组顶点组成的另一幅有向图,在传递闭包中存在一条从v指向w的边当且仅当G中w是从v可达的。我们很容易想到通过计算有向图的传递闭包来解决顶点对的可达性问题,但一般来说,一幅有向图的传递闭包中所含的边比原图中多得多,与其明确计算一幅有向图的传递闭包,不如使用深度优先搜索来实现。
算法实现:
public class TransitiveClosure{
private DirectedDFS[] all; //all[]数组中每个元素都是一个以下标为起始顶点的深度优先遍历逆后序排列
public TransitiveClosure(Digraph G) {
all = new DirectedDFS[G.V()];
for(int v = 0; v<G.V();v++)
all[v] = new DirectedDFS(G,v);
}
boolean reachable(int v,int w) {
return all[v].marked(w);
}
}
此方法不适用于实际问题中大型有向图,因为构造函数所需要的空间和V^2成正比,所需要的时间和V(V+E)成正比:共有V个DirectedDFS对象,每个所需的空间都与V成正比(他们都含有大小为V的marked[]数组并会检查E条边来计算标记)。
本质上,该方法是通过计算G的传递闭包来支持常数时间查询----传递闭包的第v行就是TransitiveClosure类中 DirectedDFS[]数组中第v个元素的marked[]数组。
用远小于平方级别的空间支持常数级别的查询的一般解决方案仍是一个有待解决的研究问题。
强连通分量问题
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量。
Kosaraju算法:
Kosaraju算法可以用来计算有向图的强连通分量。
算法思想:
- 在给定的一幅有向图G中,使用DepthFirstOrder来计算它的反向图G(R)的逆后序排列。
- 在G中进行标准的深度优先遍历,但要按照刚才得到的逆后序排列而非标准的顺序来访问所有未被标记的顶点。
- 在构造函数中,所有在同一个递归dfs()调用中被访问到的顶点都在同一个强连通分量中。
除了下面代码中标出的两行区别,Kosaraju算法的实现和求无向图的连通性问题的实现几乎完全相同。Kosaraju算法实现简单但难以理解。在知乎上看到一个对Kosaraju算法的浅显易懂的解释,可以用来帮助理解该算法的原理:https://www.zhihu.com/question/58926821/answer/163724688
算法实现:
public class KosarajuSharirSCC {
private boolean[] marked; // 已访问过的顶点
private int[] id; // 强连通分量的标识符
private int count; //强连通分量的数量
public KosarajuSharirSCC(Digraph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());//区别
for (int v : dfs.reversePost()) {//区别
if (!marked[v]) {
dfs(G, v);
count++;
}
}
}
private void dfs(Digraph G, int v) {
marked[v] = true;
id[v] = count;
for (int w : G.adj(v)) {
if (!marked[w]) dfs(G, w);
}
}
public int count() { return count; }
public boolean stronglyConnected(int v, int w) { return id[v] == id[w]; }
}