读者盆友,中午好!
我们终于走到了有向图这种非常复杂的数据结构拉。这里介绍下:
有向图的可达性、寻找有向环、有向图中基于深度优先搜索的顶点排序、拓扑排序。
直接上代码:
本博客代码示例均来自:算法 Algorithmes Forth Edition
[美] Robert Sedgewick Kevin Wayne 著 谢路云译
一、有向图的数据结构
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;
/**
* 有向图的数据类型
* Author:起舞的日子
* Date: 2020/4/25 上午10:55
*/
public class Digraph {
private final int V;
private int E;
private Bag<Integer>[] adj;
public Digraph(int V) {
this.V = V;
this.E = 0;
adj = new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = 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 int V() {
return V;
}
public int E() {
return E;
}
public void addEdge(int v, int w) {
adj[v].add(w);
E++;
}
public Iterable<Integer> adj(int v) {
return adj[v];
}
/**
* 有向图的取反
* 返回有向图的一个副本,但将其中所有的边的方向反转
* 作用:用例可以找出"指向"每个顶点的所有边
*
* @return
*/
public Digraph reverse() {
Digraph R = new Digraph(V);
for (int v = 0; v < V; v++) {
for (int w : adj[v]) {
R.addEdge(w, v);
}
}
return R;
}
}
二、有向图的可达性
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
/**
* 有向图的可达性
* Author:起舞的日子
* Date: 2020/4/25 上午11:03
*/
public class DirectedDFS {
/**
* 多点可达性:给定一副有向图和顶点的集合,回答:"是否存在一条从集合中任意顶点到达给定顶点v的有向路径?"等类似问题
*/
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];
}
public static void main(String[] args) {
Digraph G = new Digraph(new In(args[0]));
Bag<Integer> sources = new Bag<>();
for (int i = 1; i < args.length; i++) {
sources.add(Integer.parseInt(args[i]));
}
DirectedDFS reachable = new DirectedDFS(G, sources);
for (int v = 0; v < G.V(); v++) {
if (reachable.marked(v)) {
StdOut.print(v + " ");
}
}
StdOut.println();
}
/**
* 这份深度优先搜索的实现是的用例能够判断从给定的一个或者一组顶点能能够到达哪些其他顶点。
*
*/
}
三、寻找有向环
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.Stack;
/**
* 寻找有向环
* Author:起舞的日子
* Date: 2020/4/25 上午11:27
*/
public class DirectedCycle {
/**
* 为什么需要检查有向图中的环呢?
* eg:任务调度时,任务有优先级,如果x必须在y之前完成;y又必须在z之前完成;z又必须在x之前完成,那么这个问题是无解的
* 在类似的场景下要排除有向环
*/
private boolean[] marked;
private int[] edgeTo;
/**
* 有向环中的所有顶点
*/
private Stack<Integer> cycle;
/**
* 递归调用栈上所有顶点
*/
private boolean[] onStack;
public DirectedCycle(Digraph G) {
onStack = new boolean[G.V()];
edgeTo = new int[G.V()];
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
}
}
}
private void dfs(Digraph G, int v) {
onStack[v] = true;
marked[v] = true;
for (int w : G.adj(v)) {
if (this.hasCycle()) {
return;
} else if (!marked[w]) {
edgeTo[w] = v;
dfs(G, w);
} else if (onStack[w]) {
cycle = new Stack<>();
for (int x = v; x != w; x = edgeTo[x]) {
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
onStack[v] = false;
}
public boolean hasCycle() {
return cycle != null;
}
public Iterable<Integer> cycle() {
return cycle;
}
}
四、有向图中基于深度优先搜索的顶点排序
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.Stack;
/**
* 有向图中基于深度优先搜索的顶点排序
* Author:起舞的日子
* Date: 2020/4/25 上午11:40
*/
public class DepthFirstOrder {
/**
* 优先级限制下的调度问题等价于 计算有向无环图中的所有顶点的拓扑顺序
*/
private boolean[] marked;
/**
* 所有顶点的前序排列
*/
private Queue<Integer> pre;
/**
* 所有顶点的后续排列
*/
private Queue<Integer> post;
/**
* 所有顶点的逆后序排列
*/
private Stack<Integer> reversePost;
public DepthFirstOrder(Digraph G) {
pre = new Queue<>();
post = new Queue<>();
reversePost = new Stack<Integer>();
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++) {
if (!marked[v]) {
dfs(G, v);
}
}
}
private void dfs(Digraph G, int v) {
pre.enqueue(v);
marked[v] = true;
for (int w : G.adj(v)) {
if (!marked[w]) {
dfs(G, w);
}
}
post.enqueue(v);
reversePost.push(v);
}
public Iterable<Integer> pre() {
return pre;
}
public Iterable<Integer> post() {
return post;
}
public Iterable<Integer> reversePost() {
return reversePost;
}
}
五、拓扑排序
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.ST;
/**
* 有向无环图的符号图
* <p>
* 因为跟SymbolGraph几乎一模一样,就不做任何注释了
* Author:起舞的日子
* Date: 2020/4/25 上午11:55
*/
public class SymbolDigraph {
private ST<String, Integer> st;
private String[] keys;
private Digraph G;
public SymbolDigraph(String stream, String sp) {
st = new ST<>();
In in = new In(stream);
while (in.hasNextLine()) {
String[] a = in.readLine().split(sp);
for (int i = 0; i < a.length; i++) {
if (!st.contains(a[i])) {
st.put(a[i], st.size());
}
}
}
keys = new String[st.size()];
for (String name : keys) {
keys[st.get(name)] = name;
}
G = new Digraph(st.size());
in = new In(stream);
while (in.hasNextLine()) {
String[] a = in.readLine().split(sp);
int v = st.get(a[0]);
for (int i = 1; i < a.length; i++) {
G.addEdge(v, st.get(a[i]));
}
}
}
public boolean contains(String s) {
return st.contains(s);
}
public int index(String s) {
return st.get(s);
}
public String name(int v) {
return keys[v];
}
public Digraph G() {
return G;
}
}
package com.cmh.algorithm.graph;
import edu.princeton.cs.algs4.StdOut;
/**
* 拓扑排序
* Author:起舞的日子
* Date: 2020/4/25 上午11:48
*/
public class Topological {
/**
* 顶点的拓扑排序
*/
private Iterable<Integer> order;
public Topological(Digraph G) {
DirectedCycle cycleFinder = new DirectedCycle(G);
if (!cycleFinder.hasCycle()) {
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = dfs.reversePost();
}
}
public Iterable<Integer> order() {
return order;
}
public boolean isDAG() {
return order != null;
}
public static void main(String[] args) {
String filename = args[0];
String separator = args[1];
SymbolDigraph sg = new SymbolDigraph(filename, separator);
Topological top = new Topological(sg.G());
for (int v : top.order) {
StdOut.println(sg.name(v));
}
}
}
六、源码
https://github.com/cmhhcm/my2020.git
七、总结
非常的复杂,先留个印象吧