▶ 书中第四章部分程序,包括在加上自己补充的代码,在有权有向图中寻找环,Bellman - Ford 算法求最短路径,套汇算法
● 在有权有向图中寻找环
1 package package01; 2 3 import edu.princeton.cs.algs4.StdOut; 4 import edu.princeton.cs.algs4.StdRandom; 5 import edu.princeton.cs.algs4.DirectedEdge; 6 import edu.princeton.cs.algs4.EdgeWeightedDigraph; 7 import edu.princeton.cs.algs4.Stack; 8 9 public class class01 10 { 11 private boolean[] marked; 12 private DirectedEdge[] edgeTo; 13 private boolean[] onStack; // 各顶点当前是否在搜索栈中,递归回退时要清空 14 private Stack<DirectedEdge> cycle; // 存储环,若空则表示图中不存在环 15 16 public class01(EdgeWeightedDigraph G) 17 { 18 marked = new boolean[G.V()]; 19 edgeTo = new DirectedEdge[G.V()]; 20 onStack = new boolean[G.V()]; 21 for (int v = 0; v < G.V(); v++) 22 { 23 if (!marked[v]) 24 dfs(G, v); 25 } 26 } 27 28 private void dfs(EdgeWeightedDigraph G, int v) 29 { 30 marked[v] = true; 31 onStack[v] = true; 32 for (DirectedEdge e : G.adj(v)) 33 { 34 int w = e.to(); 35 if (cycle != null) // 已经有环了 36 return; 37 if (!marked[w]) 38 { 39 edgeTo[w] = e; 40 dfs(G, w); 41 } 42 else if (onStack[w]) // 该点已经遍历过,且在栈中,即有环 43 { 44 cycle = new Stack<DirectedEdge>(); 45 DirectedEdge f = e; 46 for (; f.from() != w; f = edgeTo[f.from()]) // 回退搜索栈压入 cycle 中,直到该环在搜索栈中的首元素 w 处 47 cycle.push(f); 48 cycle.push(f); // 压入环在搜索栈中的首元素 w 49 return; 50 } 51 } 52 onStack[v] = false; // 递归回退时栈要清空,但 marked 不清空 53 } 54 55 public boolean hasCycle() 56 { 57 return cycle != null; 58 } 59 60 public Iterable<DirectedEdge> cycle() 61 { 62 return cycle; 63 } 64 65 public static void main(String[] args) 66 { 67 int V = Integer.parseInt(args[0]); // 新建有边权有向图 G(E,V),再添加 F 条边 68 int E = Integer.parseInt(args[1]); 69 int F = Integer.parseInt(args[2]); 70 EdgeWeightedDigraph G = new EdgeWeightedDigraph(V); 71 int[] vertices = new int[V]; 72 for (int i = 0; i < V; i++) 73 vertices[i] = i; 74 StdRandom.shuffle(vertices); 75 for (int i = 0; i < E; i++) 76 { 77 int v = 1, w = 0; 78 for (; v >= w; v = StdRandom.uniform(V), w = StdRandom.uniform(V)); 79 double weight = StdRandom.uniform(); 80 G.addEdge(new DirectedEdge(v, w, weight)); 81 } 82 for (int i = 0; i < F; i++) 83 { 84 int v = StdRandom.uniform(V), w = StdRandom.uniform(V); 85 double weight = StdRandom.uniform(0.0, 1.0); 86 G.addEdge(new DirectedEdge(v, w, weight)); 87 } 88 89 StdOut.println(G); // 原图 90 class01 finder = new class01(G); // 搜索环 91 if (finder.hasCycle()) 92 { 93 StdOut.print("Cycle: "); 94 for (DirectedEdge e : finder.cycle()) 95 StdOut.print(e + " "); 96 StdOut.println(); 97 } 98 else 99 StdOut.println("No directed cycle"); 100 } 101 }
● Bellman - Ford 算法求最短路径
1 package package01; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.DirectedEdge; 6 import edu.princeton.cs.algs4.EdgeWeightedDigraph; 7 import edu.princeton.cs.algs4.EdgeWeightedDirectedCycle; 8 import edu.princeton.cs.algs4.Stack; 9 import edu.princeton.cs.algs4.Queue; 10 11 public class class01 12 { 13 private double[] distTo; // 起点到各顶点的距离 14 private DirectedEdge[] edgeTo; // 起点到各顶点的最后一条边 15 private boolean[] onQueue; // 各顶点当前是否在搜索队中 16 private Queue<Integer> queue; // 搜索队列 17 private int cost; // 调用函数 relax 的次数 18 private Iterable<DirectedEdge> cycle; // 存储负环,若空则表示图中不存在负环 19 20 public class01(EdgeWeightedDigraph G, int s) 21 { 22 distTo = new double[G.V()]; 23 edgeTo = new DirectedEdge[G.V()]; 24 onQueue = new boolean[G.V()]; 25 for (int v = 0; v < G.V(); v++) 26 distTo[v] = Double.POSITIVE_INFINITY; 27 distTo[s] = 0.0; 28 queue = new Queue<Integer>(); 29 for (queue.enqueue(s), onQueue[s] = true; !queue.isEmpty() && !hasNegativeCycle();) // 存在负环则停止搜索 30 { 31 int v = queue.dequeue(); // 每次从搜索队列中拿走一个顶点,松弛以该顶点为起点的边 32 onQueue[v] = false; 33 relax(G, v); 34 } 35 } 36 37 private void relax(EdgeWeightedDigraph G, int v) 38 { 39 for (DirectedEdge e : G.adj(v)) 40 { 41 int w = e.to(); 42 if (distTo[w] > distTo[v] + e.weight()) // 加入这条边会使起点到 w 的距离变短 43 { 44 distTo[w] = distTo[v] + e.weight(); 45 edgeTo[w] = e; 46 if (!onQueue[w]) // 注意若 w 已经在搜索队列中则不做任何改变 47 { 48 queue.enqueue(w); 49 onQueue[w] = true; 50 } 51 } 52 if (cost++ % G.V() == 0) // 每当松弛了 V 的倍数次时检查是否存在负环 53 { 54 findNegativeCycle(); 55 if (hasNegativeCycle()) 56 return; 57 } 58 } 59 } 60 61 private void findNegativeCycle() // 利用类 EdgeWeightedDirectedCycle 来找环 62 { 63 int V = edgeTo.length; 64 EdgeWeightedDigraph spt = new EdgeWeightedDigraph(V); 65 for (int v = 0; v < V; v++) 66 { 67 if (edgeTo[v] != null) 68 spt.addEdge(edgeTo[v]); 69 } 70 EdgeWeightedDirectedCycle finder = new EdgeWeightedDirectedCycle(spt); 71 cycle = finder.cycle(); 72 } 73 74 public boolean hasNegativeCycle() 75 { 76 return cycle != null; 77 } 78 79 public Iterable<DirectedEdge> negativeCycle() 80 { 81 return cycle; 82 } 83 84 public boolean hasPathTo(int v) 85 { 86 return distTo[v] < Double.POSITIVE_INFINITY; 87 } 88 89 public double distTo(int v) 90 { 91 if (hasNegativeCycle()) 92 throw new UnsupportedOperationException(" <distTo> Negative cost cycle exists. "); 93 return distTo[v]; 94 } 95 96 public Iterable<DirectedEdge> pathTo(int v) 97 { 98 if (hasNegativeCycle()) 99 throw new UnsupportedOperationException(" <pathTo> Negative cost cycle exists. "); 100 if (!hasPathTo(v)) 101 return null; 102 Stack<DirectedEdge> path = new Stack<DirectedEdge>(); 103 for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) 104 path.push(e); 105 return path; 106 } 107 108 public static void main(String[] args) 109 { 110 In in = new In(args[0]); 111 int s = Integer.parseInt(args[1]); 112 EdgeWeightedDigraph G = new EdgeWeightedDigraph(in); 113 class01 sp = new class01(G, s); 114 if (sp.hasNegativeCycle()) 115 { 116 for (DirectedEdge e : sp.negativeCycle()) 117 StdOut.println(e); 118 } 119 else 120 { 121 for (int v = 0; v < G.V(); v++) 122 { 123 if (sp.hasPathTo(v)) 124 { 125 StdOut.printf("%d to %d (%5.2f) ", s, v, sp.distTo(v)); 126 for (DirectedEdge e : sp.pathTo(v)) 127 StdOut.print(e + " "); 128 StdOut.println(); 129 } 130 else 131 StdOut.printf("%d to %d no path ", s, v); 132 } 133 } 134 } 135 }
● 套汇,本质是寻找负环
1 package package01; 2 3 import edu.princeton.cs.algs4.StdIn; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.DirectedEdge; 6 import edu.princeton.cs.algs4.EdgeWeightedDigraph; 7 import edu.princeton.cs.algs4.BellmanFordSP; 8 9 public class class01 10 { 11 private class01() {} 12 13 public static void main(String[] args) 14 { 15 int V = StdIn.readInt(); // 顶点数 16 String[] name = new String[V]; 17 EdgeWeightedDigraph G = new EdgeWeightedDigraph(V); 18 for (int v = 0; v < V; v++) // 建图 19 { 20 name[v] = StdIn.readString(); 21 for (int w = 0; w < V; w++) 22 { 23 double rate = StdIn.readDouble(); 24 DirectedEdge e = new DirectedEdge(v, w, -Math.log(rate));// 汇率取负对数,x1x2x3 < 1 -> -logx1 -logx2 -logx3 < 0 25 G.addEdge(e); 26 } 27 } 28 BellmanFordSP spt = new BellmanFordSP(G, 0); // 用 BF 算法找负环 29 if (spt.hasNegativeCycle()) 30 { 31 double stake = 1000.0; 32 for (DirectedEdge e : spt.negativeCycle()) 33 { 34 StdOut.printf("%10.5f %s ", stake, name[e.from()]); 35 stake *= Math.exp(-e.weight()); 36 StdOut.printf("= %10.5f %s ", stake, name[e.to()]); 37 } 38 } 39 else 40 StdOut.println("No arbitrage opportunity"); 41 } 42 }