package com.jwetherell.algorithms.graph; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Queue; import com.jwetherell.algorithms.data_structures.Graph; /** * Dijkstra's shortest path. Only works on non-negative path weights. Returns a * tuple of total cost of shortest path and the path. * * Worst case: O(|E| + |V| log |V|) * * https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm * * @author Justin Wetherell */ public class Dijkstra { private Dijkstra() { } public static Map, Graph.CostPathPair> getShortestPaths(Graph graph, Graph.Vertex start) { final Map, List>> paths = new HashMap, List>>(); final Map, Graph.CostVertexPair> costs = new HashMap, Graph.CostVertexPair>(); getShortestPath(graph, start, null, paths, costs); final Map, Graph.CostPathPair> map = new HashMap, Graph.CostPathPair>(); for (Graph.CostVertexPair pair : costs.values()) { int cost = pair.getCost(); Graph.Vertex vertex = pair.getVertex(); List> path = paths.get(vertex); map.put(vertex, new Graph.CostPathPair(cost, path)); } return map; } public static Graph.CostPathPair getShortestPath(Graph graph, Graph.Vertex start, Graph.Vertex end) { if (graph == null) throw (new NullPointerException("Graph must be non-NULL.")); // Dijkstra's algorithm only works on positive cost graphs final boolean hasNegativeEdge = checkForNegativeEdges(graph.getVertices()); if (hasNegativeEdge) throw (new IllegalArgumentException("Negative cost Edges are not allowed.")); final Map, List>> paths = new HashMap, List>>(); final Map, Graph.CostVertexPair> costs = new HashMap, Graph.CostVertexPair>(); return getShortestPath(graph, start, end, paths, costs); } private static Graph.CostPathPair getShortestPath(Graph graph, Graph.Vertex start, Graph.Vertex end, Map, List>> paths, Map, Graph.CostVertexPair> costs) { if (graph == null) throw (new NullPointerException("Graph must be non-NULL.")); if (start == null) throw (new NullPointerException("start must be non-NULL.")); // Dijkstra's algorithm only works on positive cost graphs boolean hasNegativeEdge = checkForNegativeEdges(graph.getVertices()); if (hasNegativeEdge) throw (new IllegalArgumentException("Negative cost Edges are not allowed.")); for (Graph.Vertex v : graph.getVertices()) paths.put(v, new ArrayList>()); for (Graph.Vertex v : graph.getVertices()) { if (v.equals(start)) costs.put(v, new Graph.CostVertexPair(0, v)); else costs.put(v, new Graph.CostVertexPair(Integer.MAX_VALUE, v)); } final Queue> unvisited = new PriorityQueue>(); unvisited.add(costs.get(start)); while (!unvisited.isEmpty()) { final Graph.CostVertexPair pair = unvisited.remove(); final Graph.Vertex vertex = pair.getVertex(); // Compute costs from current vertex to all reachable vertices which haven't been visited for (Graph.Edge e : vertex.getEdges()) { final Graph.CostVertexPair toPair = costs.get(e.getToVertex()); // O(1) final Graph.CostVertexPair lowestCostToThisVertex = costs.get(vertex); // O(1) final int cost = lowestCostToThisVertex.getCost() + e.getCost(); if (toPair.getCost() == Integer.MAX_VALUE) { // Haven't seen this vertex yet // Need to remove the pair and re-insert, so the priority queue keeps it's invariants unvisited.remove(toPair); // O(n) toPair.setCost(cost); unvisited.add(toPair); // O(log n) // Update the paths List> set = paths.get(e.getToVertex()); // O(log n) set.addAll(paths.get(e.getFromVertex())); // O(log n) set.add(e); } else if (cost < toPair.getCost()) { // Found a shorter path to a reachable vertex // Need to remove the pair and re-insert, so the priority queue keeps it's invariants unvisited.remove(toPair); // O(n) toPair.setCost(cost); unvisited.add(toPair); // O(log n) // Update the paths List> set = paths.get(e.getToVertex()); // O(log n) set.clear(); set.addAll(paths.get(e.getFromVertex())); // O(log n) set.add(e); } } // Termination conditions if (end != null && vertex.equals(end)) { // We are looking for shortest path to a specific vertex, we found it. break; } } if (end != null) { final Graph.CostVertexPair pair = costs.get(end); final List> set = paths.get(end); return (new Graph.CostPathPair(pair.getCost(), set)); } return null; } private static boolean checkForNegativeEdges(Collection> vertitices) { for (Graph.Vertex v : vertitices) { for (Graph.Edge e : v.getEdges()) { if (e.getCost() < 0) return true; } } return false; } }