有向图
有向图
在有向图中,边是单向的:每条边所连接的两个顶点都是一个有序对,它们的邻接性是单向的。
有向图的数据类型
有向图 API:
public class Digraph {
Digraph(int V); // 创建一幅含有 V 个顶点但不含有边的有向图
Digraph(In in); // 从标准输入流中读取一幅有向图
int V(); // 顶点数
int E(); // 边数
void addEdge(int v, int w); // 添加一条边 v -> w
Iterable<Integer> adj(int v); // 由 v 指出的边所连接的所有顶点
Digraph reverse(); // 该图的方向图
String toString();
}
// 有向图
public class Digraph {
private int V;
private int E;
private Bag<Integer>[] adj;
public Digraph(int V) {
this.V = V;
this.E = 0;
this.adj = new Bag[V];
for (int i = 0; i < adj.length; i++) {
adj[i] = new Bag<>();
}
}
public Digraph(In in) {
this(in.readInt());
int E = in.readInt(); // 局部变量
for (int i = 0; i < E; i++) {
int v = in.readInt();
int w = in.readInt();
addEdge(v, w);
}
}
public void addEdge(int v, int w) {
adj[v].add(w);
E++;
}
public Iterable<Integer> adj(int v) {
return adj[v];
}
public Digraph reverse() {
Digraph digraph = new Digraph(V);
for (int v = 0; v < v; v++) {
for (int w : adj[v]) {
digraph.addEdge(w, v);
}
}
return digraph;
}
public int V() {
return V;
}
public int E() {
return E;
}
}
单点可达性
单点可达性:给定一幅有向图和一个起点 s,回答 “是否存在一条从 s 到达给定顶点 v 的有向路径”
多点可达性:给定一幅有向图和顶点的集合,回答 “是否存在一条从集合中的任意顶点到达给定顶点 v 的有向路径”
有向图可达性 API:
public class DirectedDFS {
DirectedDFS(Digraph G, int s); // 在 G 中找到从 s 可达的所有顶点
DirectedDFS(Digraph G, Iterable<Integer> souces); // 在 G 中找到从 source 中的所有顶点可达的所有顶点
boolean marked(int v); // v 是可达的吗
}
深度优先搜索:
public class DirectedDFS {
private boolean[] marked;
public DirectedDFS(Digraph G, int s) {
marked = new boolean[G.V()];
dfs(G, s);
}
public DirectedDFS(Digraph G, Iterable<Integer> sources) {
marked = new boolean[G.V()];
for (int s : sources) {
if (!marked[s]) {
dfs(G, s);
}
}
}
private void dfs(Digraph 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];
}
}
有向路径
单点有向路径:给定一幅有向图和一个起点 s,回答 “从 s 到给定目的顶点 v 是否存在一条有向路径?如果有,找出这条路径”
单点最短有向路径:给定一幅有向图和一个起点 s,回答 “从 s 到给定目的顶点 v 是否存在一条有向路径?如果有,找出其中最短的那条”
有向路径 API:
public interface DirectedPaths {
boolean hasPathTo(int v);
Iterable<Integer> pathTo(int v);
}
深度优先搜索(单点有向路径):
public class DepthFirstDirectedPaths implements DirectedPaths {
private boolean[] marked;
private int s;
private int[] pathTo;
public DepthFirstDirectedPaths(Digraph G, int s) {
this.marked = new boolean[G.V()];
this.s = s;
this.pathTo = new int[G.V()];
dfs(G, s);
}
private void dfs(Digraph G, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
if (!marked[w]) {
pathTo[w] = v;
dfs(G, w);
}
}
}
@Override
public boolean hasPathTo(int v) {
return marked[v];
}
@Override
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
Stack<Integer> stack = new Stack<>();
while (v != s) {
stack.push(v);
v = pathTo[v];
}
stack.push(s);
return stack;
}
}
广度优先搜索(单点最短有向路径):
public class BreadthFirstDirectedPaths implements DirectedPaths {
private boolean[] marked;
private int s;
private int[] pathTo;
public BreadthFirstDirectedPaths(Digraph G, int s) {
this.marked = new boolean[G.V()];
this.s = s;
this.pathTo = new int[G.V()];
bfs(G, s);
}
private void bfs(Digraph G, int s) {
Queue<Integer> queue = new Queue<>();
queue.add(s);
marked[s] = true;
while (!queue.isEmpty()) {
int v = queue.remove();
for (int w : G.adj(v)) {
if (!marked[w]) {
pathTo[w] = v;
queue.add(w);
marked[w] = true;
}
}
}
}
@Override
public boolean hasPathTo(int v) {
return marked[v];
}
@Override
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
Stack<Integer> stack = new Stack<>();
while (v != s) {
stack.push(v);
v = pathTo[v];
}
stack.push(v);
return stack;
}
}
有向无环图
优先级限制下的调度问题:给定一组需要完成的任务,以及一组关于任务完成的先后次序的优先级限制。
在满足限制条件的前提下应该如何安排并完成所有任务?
拓扑排序:给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素。
有向环检测
一般来说,如果一个有优先级限制的问题中存在有向环,那么这个问题肯定是无解的。
有向环检测:给定的有向图中包含环吗?如果有,按照路径的方向从某个顶点返回自己来找到环上的所有顶点。
有向环 API:
public class DirectedCycle {
DirectedCycle(Digraph G); // 寻找有向环的构造函数
boolean hasCycle(); // G 是否含有有向环
Iterable<Integer> cycle(); // 有向环中的所有顶点,不存在则返回 null
}
深度优先搜索:
public class DepthFirstDirectedCycle implements DirectedCycle {
private boolean[] marked;
private boolean[] onStack; // 标记递归栈中的顶点
private int[] pathTo;
private Stack<Integer> cycle;
public DepthFirstDirectedCycle(Digraph G) {
marked = new boolean[G.V()];
onStack = new boolean[G.V()];
pathTo = new int[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
}
}
}
private void dfs(Digraph G, int v) {
marked[v] = true;
onStack[v] = true;
for (int w : G.adj(v)) {
if (hasCycle()) {
return;
}
if (!marked[w]) {
pathTo[w] = v;
dfs(G, w);
} else if (onStack[w]) {
cycle = new Stack<>();
for (int x = v; x != w; x = pathTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
onStack[v] = false;
}
@Override
public boolean hasCycle() {
return cycle != null;
}
@Override
public Iterable<Integer> cycle() {
return cycle;
}
}
拓扑排序
优先级限制下的调度问题等价于计算有向无环图中的所有顶点的拓扑顺序。
一幅有向图的拓扑排序即为所有顶点的逆后序排序。
拓扑排序 API:
public class TopoLogical {
TopoLogical(Digraph G); // 拓扑排序的构造函数
boolean isDAG(); // G 是有向无环图吗
Iterable<Integer> order(); // 拓扑有序的所有顶点
}
public class DepthFirstOrder {
private boolean[] marked;
private Queue<Integer> pre; // 前序
private Queue<Integer> post; // 后序
private Stack<Integer> reversePost; // 逆后序
public DepthFirstOrder(Digraph G) {
int V = G.V();
marked = new boolean[V];
pre = new Queue<>(V);
post = new Queue<>(V);
reversePost = new Stack<>(V);
for (int v = 0; v < V; v++) {
if (!marked[v]) {
dfs(G, v);
}
}
}
private void dfs(Digraph G, int v) {
marked[v] = true;
pre.add(v);
for (int w : G.adj(v)) {
if (!marked[w]) {
dfs(G, w);
}
}
post.add(v);
reversePost.push(v);
}
public Iterable<Integer> pre() {
return pre;
}
public Iterable<Integer> post() {
return post;
}
public Iterable<Integer> reversePost() {
return reversePost;
}
}
public class TopoLogical {
private Iterable<Integer> order;
public TopoLogical(Digraph G) {
DirectedCycle directedCycle = new DepthFirstDirectedCycle(G);
if (!directedCycle.hasCycle()) {
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
order = depthFirstOrder.reversePost();
}
}
// 是不是有向无环图
public boolean isDAG() {
return order != null;
}
// 拓扑排序/逆后序
public Iterable<Integer> order() {
return order;
}
}
强连通性
强连通性:如果两个顶点 v 和 w 是互相可达的,则称它们为强连通的。
如果一幅有向图中的任意两个顶点都是强连通的,则称这幅有向图也是强连通的。
强连通分量 API:
public cass SCC {
SCC(Digraph G); // 预处理构造函数
boolean stronglyConnected(int v, int w); // v 和 w 是强连通的吗
int count(); // 图中的强连通分量的总数
int id(int v); // v 所在的强连通分量的标识符
}
Kosaraju 算法:使用深度优先搜索查找给定有向图 G 的方向图 \(G^R\),根据由此得到的所有顶点的逆后序再次使用深度优先搜索处理有向图 G(Kosaraju 算法),其构造函数中的每一次递归调用所标记的顶点都在同一个强连通分量之中。
public class KosarajuSCC implements SCC {
private boolean[] marked;
private int[] ids;
private int count;
public KosarajuSCC(Digraph G) {
marked = new boolean[G.V()];
ids = new int[G.V()];
count = 0;
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G.reverse());
Iterable<Integer> order = depthFirstOrder.reversePost();
for (int v : order) {
if (!marked[v]) {
dfs(G, v, count);
count++;
}
}
}
private void dfs(Digraph G, int v, int id) {
marked[v] = true;
ids[v] = id;
for (int w : G.adj(v)) {
if (marked[w]) {
dfs(G, w, id);
}
}
}
@Override
public boolean stronglyConnected(int v, int w) {
return ids[v] == ids[w];
}
@Override
public int count() {
return count;
}
@Override
public int id(int v) {
return ids[v];
}
}