|
1 | 1 | package chapter4;
|
2 | 2 |
|
3 | 3 | import java.util.Arrays;
|
| 4 | +import java.util.Collections; |
| 5 | +import java.util.HashMap; |
| 6 | +import java.util.LinkedList; |
4 | 7 | import java.util.List;
|
| 8 | +import java.util.Map; |
| 9 | +import java.util.PriorityQueue; |
| 10 | +import java.util.function.IntConsumer; |
5 | 11 |
|
6 | 12 | public class WeightedGraph<V> extends Graph<V, WeightedEdge> {
|
7 | 13 |
|
@@ -38,6 +44,138 @@ public String toString() {
|
38 | 44 | return sb.toString();
|
39 | 45 | }
|
40 | 46 |
|
| 47 | + public static double totalWeight(List<WeightedEdge> path) { |
| 48 | + return path.stream().mapToDouble(we -> we.weight).sum(); |
| 49 | + } |
| 50 | + |
| 51 | + // Find the minimum-spanning tree of this graph using Jarnik's algorithm |
| 52 | + // *start* is the vertex index to start the search at |
| 53 | + public List<WeightedEdge> mst(int start) { |
| 54 | + LinkedList<WeightedEdge> result = new LinkedList<>(); // final mst |
| 55 | + PriorityQueue<WeightedEdge> pq = new PriorityQueue(); |
| 56 | + boolean[] visited = new boolean[getVertexCount()]; // where we've been |
| 57 | + |
| 58 | + // this is like a "visit" inner function |
| 59 | + IntConsumer visit = index -> { |
| 60 | + visited[index] = true; // mark as visited |
| 61 | + for (WeightedEdge edge : edgesOf(index)) { |
| 62 | + // add all edges coming from here to pq |
| 63 | + if (!visited[edge.v]) { |
| 64 | + pq.offer(edge); |
| 65 | + } |
| 66 | + } |
| 67 | + }; |
| 68 | + |
| 69 | + visit.accept(start); // the first vertex is where everything begins |
| 70 | + while (!pq.isEmpty()) { // keep going while there are edges to process |
| 71 | + WeightedEdge edge = pq.poll(); |
| 72 | + if (visited[edge.v]) { |
| 73 | + continue; // don't ever revisit |
| 74 | + } |
| 75 | + // this is the current smallest, so add it to solution |
| 76 | + result.add(edge); |
| 77 | + visit.accept(edge.v); // visit where this connects |
| 78 | + } |
| 79 | + |
| 80 | + return result; |
| 81 | + } |
| 82 | + |
| 83 | + public void printWeightedPath(List<WeightedEdge> wp) { |
| 84 | + for (WeightedEdge edge : wp) { |
| 85 | + System.out.println(vertexAt(edge.u) + " " + edge.weight + "> " + vertexAt(edge.v)); |
| 86 | + } |
| 87 | + System.out.println("Total Weight: " + totalWeight(wp)); |
| 88 | + } |
| 89 | + |
| 90 | + public static final class DijkstraNode implements Comparable<DijkstraNode> { |
| 91 | + public final int vertex; |
| 92 | + public final double distance; |
| 93 | + |
| 94 | + public DijkstraNode(int vertex, double distance) { |
| 95 | + this.vertex = vertex; |
| 96 | + this.distance = distance; |
| 97 | + } |
| 98 | + |
| 99 | + @Override |
| 100 | + public int compareTo(DijkstraNode other) { |
| 101 | + Double mine = distance; |
| 102 | + Double theirs = other.distance; |
| 103 | + return mine.compareTo(theirs); |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + public static final class DijkstraResult { |
| 108 | + public final double[] distances; |
| 109 | + public final Map<Integer, WeightedEdge> pathMap; |
| 110 | + |
| 111 | + public DijkstraResult(double[] distances, Map<Integer, WeightedEdge> pathMap) { |
| 112 | + this.distances = distances; |
| 113 | + this.pathMap = pathMap; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + public DijkstraResult dijkstra(V root) { |
| 118 | + int first = indexOf(root); // find starting index |
| 119 | + // distances are unknown at first |
| 120 | + double[] distances = new double[getVertexCount()]; |
| 121 | + distances[first] = 0; // root's distance to root is 0 |
| 122 | + boolean[] visited = new boolean[getVertexCount()]; // where we've been |
| 123 | + visited[first] = true; |
| 124 | + // how we got to each vertex |
| 125 | + HashMap<Integer, WeightedEdge> pathMap = new HashMap<>(); |
| 126 | + PriorityQueue<DijkstraNode> pq = new PriorityQueue<>(); |
| 127 | + pq.offer(new DijkstraNode(first, 0)); |
| 128 | + |
| 129 | + while (!pq.isEmpty()) { |
| 130 | + int u = pq.poll().vertex; // explore the next closest vertex |
| 131 | + double distU = distances[u]; // should already have seen it |
| 132 | + // look at every edge/vertex from the vertex in question |
| 133 | + for (WeightedEdge we : edgesOf(u)) { |
| 134 | + // the old distance to this vertex |
| 135 | + double distV = distances[we.v]; |
| 136 | + double pathWeight = we.weight + distU; |
| 137 | + // new vertex or found shorter path? |
| 138 | + if (!visited[we.v] || (distV > pathWeight)) { |
| 139 | + visited[we.v] = true; |
| 140 | + // update the distance to this vertex |
| 141 | + distances[we.v] = pathWeight; |
| 142 | + // update the edge on the shortest path to this vertex |
| 143 | + pathMap.put(we.v, we); |
| 144 | + // explore it in the future |
| 145 | + pq.offer(new DijkstraNode(we.v, pathWeight)); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + return new DijkstraResult(distances, pathMap); |
| 151 | + } |
| 152 | + |
| 153 | + // Helper function to get easier access to dijkstra results |
| 154 | + public Map<V, Double> distanceArrayToDistanceMap(double[] distances) { |
| 155 | + HashMap<V, Double> distanceMap = new HashMap<>(); |
| 156 | + for (int i = 0; i < distances.length; i++) { |
| 157 | + distanceMap.put(vertexAt(i), distances[i]); |
| 158 | + } |
| 159 | + return distanceMap; |
| 160 | + } |
| 161 | + |
| 162 | + // Takes a map of edges to reach each node and return a list of |
| 163 | + // edges that goes from *start* to *end* |
| 164 | + public static List<WeightedEdge> pathMapToPath(int start, int end, Map<Integer, WeightedEdge> pathMap) { |
| 165 | + if (pathMap.size() == 0) { |
| 166 | + return List.of(); |
| 167 | + } |
| 168 | + LinkedList<WeightedEdge> path = new LinkedList<>(); |
| 169 | + WeightedEdge edge = pathMap.get(end); |
| 170 | + path.add(edge); |
| 171 | + while (edge.u != start) { |
| 172 | + edge = pathMap.get(edge.u); |
| 173 | + path.add(edge); |
| 174 | + } |
| 175 | + Collections.reverse(path); |
| 176 | + return path; |
| 177 | + } |
| 178 | + |
41 | 179 | // Test basic Graph construction
|
42 | 180 | public static void main(String[] args) {
|
43 | 181 | // Represents the 15 largest MSAs in the United States
|
@@ -73,6 +211,23 @@ public static void main(String[] args) {
|
73 | 211 | cityGraph2.addEdge("Philadelphia", "Washington", 123);
|
74 | 212 |
|
75 | 213 | System.out.println(cityGraph2);
|
| 214 | + |
| 215 | + List<WeightedEdge> mst = cityGraph2.mst(0); |
| 216 | + cityGraph2.printWeightedPath(mst); |
| 217 | + |
| 218 | + System.out.println(); // spacing |
| 219 | + |
| 220 | + DijkstraResult dijkstraResult = cityGraph2.dijkstra("Los Angeles"); |
| 221 | + Map<String, Double> nameDistance = cityGraph2.distanceArrayToDistanceMap(dijkstraResult.distances); |
| 222 | + System.out.println("Distances from Los Angeles:"); |
| 223 | + nameDistance.forEach((name, distance) -> System.out.println(name + " : " + distance)); |
| 224 | + |
| 225 | + System.out.println(); // spacing |
| 226 | + |
| 227 | + System.out.println("Shortest path from Los Angeles to Boston:"); |
| 228 | + List<WeightedEdge> path = pathMapToPath(cityGraph2.indexOf("Los Angeles"), cityGraph2.indexOf("Boston"), |
| 229 | + dijkstraResult.pathMap); |
| 230 | + cityGraph2.printWeightedPath(path); |
76 | 231 | }
|
77 | 232 |
|
78 | 233 | }
|
0 commit comments