diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java
index f1639bc04..63de9dcbb 100644
--- a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java
+++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java
@@ -1,9 +1,16 @@
/**
- * This file contains an implementation of Dijkstra's shortest path algorithm from a start node to a
- * specific ending node. Dijkstra can also be modified to find the shortest path between a starting
- * node and all other nodes in the graph. However, in this implementation since we're only going
- * from a starting node to an ending node we can employ an optimization to stop early once we've
- * visited all the neighbors of the ending node.
+ * Dijkstra's Shortest Path — Adjacency List (Lazy)
+ *
+ *
Finds the shortest path from a source node to a target node in a weighted
+ * directed graph with non-negative edge weights. Uses a lazy approach with a
+ * standard {@link java.util.PriorityQueue}: instead of decreasing keys, stale
+ * entries are simply skipped when polled.
+ *
+ *
Stops early once the target node is settled, so it does not necessarily
+ * visit the entire graph.
+ *
+ *
Time: O(E log E)
+ *
Space: O(V + E)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
@@ -12,158 +19,125 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
public class DijkstrasShortestPathAdjacencyList {
- // Small epsilon value to comparing double values.
- private static final double EPS = 1e-6;
-
- // An edge class to represent a directed edge
- // between two nodes with a certain cost.
- public static class Edge {
- double cost;
- int from, to;
-
- public Edge(int from, int to, double cost) {
- this.from = from;
- this.to = to;
- this.cost = cost;
- }
- }
-
- // Node class to track the nodes to visit while running Dijkstra's
- public static class Node {
- int id;
- double value;
-
- public Node(int id, double value) {
- this.id = id;
- this.value = value;
- }
- }
+ private final int n;
+ private final List> graph;
- private int n;
private double[] dist;
private Integer[] prev;
- private List> graph;
-
- private Comparator comparator =
- new Comparator() {
- @Override
- public int compare(Node node1, Node node2) {
- if (Math.abs(node1.value - node2.value) < EPS) return 0;
- return (node1.value - node2.value) > 0 ? +1 : -1;
- }
- };
- /**
- * Initialize the solver by providing the graph size and a starting node. Use the {@link #addEdge}
- * method to actually add edges to the graph.
- *
- * @param n - The number of nodes in the graph.
- */
public DijkstrasShortestPathAdjacencyList(int n) {
this.n = n;
- createEmptyGraph();
- }
-
- public DijkstrasShortestPathAdjacencyList(int n, Comparator comparator) {
- this(n);
- if (comparator == null) throw new IllegalArgumentException("Comparator cannot be null");
- this.comparator = comparator;
+ this.graph = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ graph.add(new ArrayList<>());
+ }
}
/**
- * Adds a directed edge to the graph.
- *
- * @param from - The index of the node the directed edge starts at.
- * @param to - The index of the node the directed edge end at.
- * @param cost - The cost of the edge.
+ * Adds a directed edge from node {@code from} to node {@code to} with the given cost.
*/
public void addEdge(int from, int to, int cost) {
- graph.get(from).add(new Edge(from, to, cost));
- }
-
- // Use {@link #addEdge} method to add edges to the graph and use this method
- // to retrieve the constructed graph.
- public List> getGraph() {
- return graph;
+ graph.get(from).add(new int[] {to, cost});
}
/**
- * Reconstructs the shortest path (of nodes) from 'start' to 'end' inclusive.
+ * Runs Dijkstra's algorithm from {@code start} to {@code end}.
*
- * @return An array of nodes indexes of the shortest path from 'start' to 'end'. If 'start' and
- * 'end' are not connected then an empty array is returned.
+ * @return the shortest distance, or {@code Double.POSITIVE_INFINITY} if unreachable.
*/
- public List reconstructPath(int start, int end) {
- if (end < 0 || end >= n) throw new IllegalArgumentException("Invalid node index");
- if (start < 0 || start >= n) throw new IllegalArgumentException("Invalid node index");
- double dist = dijkstra(start, end);
- List path = new ArrayList<>();
- if (dist == Double.POSITIVE_INFINITY) return path;
- for (Integer at = end; at != null; at = prev[at]) path.add(at);
- Collections.reverse(path);
- return path;
- }
-
- // Run Dijkstra's algorithm on a directed graph to find the shortest path
- // from a starting node to an ending node. If there is no path between the
- // starting node and the destination node the returned value is set to be
- // Double.POSITIVE_INFINITY.
public double dijkstra(int start, int end) {
- // Maintain an array of the minimum distance to each node
dist = new double[n];
Arrays.fill(dist, Double.POSITIVE_INFINITY);
dist[start] = 0;
- // Keep a priority queue of the next most promising node to visit.
- PriorityQueue pq = new PriorityQueue<>(2 * n, comparator);
- pq.offer(new Node(start, 0));
-
- // Array used to track which nodes have already been visited.
- boolean[] visited = new boolean[n];
prev = new Integer[n];
+ boolean[] visited = new boolean[n];
+
+ // PQ entries: {nodeId, distance}
+ PriorityQueue pq = new PriorityQueue<>((a, b) -> Double.compare(a[1], b[1]));
+ pq.offer(new double[] {start, 0});
while (!pq.isEmpty()) {
- Node node = pq.poll();
- visited[node.id] = true;
-
- // We already found a better path before we got to
- // processing this node so we can ignore it.
- if (dist[node.id] < node.value) continue;
-
- List edges = graph.get(node.id);
- for (int i = 0; i < edges.size(); i++) {
- Edge edge = edges.get(i);
-
- // You cannot get a shorter path by revisiting
- // a node you have already visited before.
- if (visited[edge.to]) continue;
-
- // Relax edge by updating minimum cost if applicable.
- double newDist = dist[edge.from] + edge.cost;
- if (newDist < dist[edge.to]) {
- prev[edge.to] = edge.from;
- dist[edge.to] = newDist;
- pq.offer(new Node(edge.to, dist[edge.to]));
+ double[] entry = pq.poll();
+ int nodeId = (int) entry[0];
+ visited[nodeId] = true;
+
+ // Skip stale entries.
+ if (entry[1] > dist[nodeId]) {
+ continue;
+ }
+
+ for (int[] edge : graph.get(nodeId)) {
+ int to = edge[0];
+ int cost = edge[1];
+ if (visited[to]) {
+ continue;
+ }
+ double newDist = dist[nodeId] + cost;
+ if (newDist < dist[to]) {
+ dist[to] = newDist;
+ prev[to] = nodeId;
+ pq.offer(new double[] {to, newDist});
}
}
- // Once we've visited all the nodes spanning from the end
- // node we know we can return the minimum distance value to
- // the end node because it cannot get any better after this point.
- if (node.id == end) return dist[end];
+
+ if (nodeId == end) {
+ return dist[end];
+ }
}
- // End node is unreachable
+
return Double.POSITIVE_INFINITY;
}
- // Construct an empty graph with n nodes including the source and sink nodes.
- private void createEmptyGraph() {
- graph = new ArrayList<>(n);
- for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
+ /**
+ * Returns the shortest path from {@code start} to {@code end} as a list of node ids,
+ * or an empty list if unreachable.
+ */
+ public List reconstructPath(int start, int end) {
+ double d = dijkstra(start, end);
+ if (d == Double.POSITIVE_INFINITY) {
+ return List.of();
+ }
+ List path = new ArrayList<>();
+ for (Integer at = end; at != null; at = prev[at]) {
+ path.add(at);
+ }
+ Collections.reverse(path);
+ return path;
+ }
+
+ // ==================== Main ====================
+
+ //
+ // 0 ---5---> 1 ---2---> 3
+ // | | ^
+ // 1 3 |
+ // | | 1
+ // v v |
+ // 2 ---6---> 4 ---------
+ //
+ // Shortest path 0 -> 3: [0, 1, 3] cost 7
+ // Shortest path 0 -> 4: [0, 2, 4] cost 7
+ //
+ public static void main(String[] args) {
+ DijkstrasShortestPathAdjacencyList solver = new DijkstrasShortestPathAdjacencyList(5);
+
+ solver.addEdge(0, 1, 5);
+ solver.addEdge(0, 2, 1);
+ solver.addEdge(1, 3, 2);
+ solver.addEdge(1, 4, 3);
+ solver.addEdge(2, 4, 6);
+ solver.addEdge(4, 3, 1);
+
+ System.out.println("Path 0->3: " + solver.reconstructPath(0, 3));
+ System.out.printf("Cost 0->3: %.0f%n", solver.dijkstra(0, 3));
+
+ System.out.println("Path 0->4: " + solver.reconstructPath(0, 4));
+ System.out.printf("Cost 0->4: %.0f%n", solver.dijkstra(0, 4));
}
}