1. Problem Definition:
Given an edge-weighted digraph, find the shortest path from s to t.
2. Different Vertices:
-- Source-sink: from one vertex to another.
-- Single source: from one vertex to every other.
-- All pairs: between all pairs of vertices.
3. Weighted directed edge: implementation in Java
public class DirectedEdge { private final int v, w; private final double weight; public DirectedEdge(int v, int w, double weight) { this.v = v; this.w = w; this.weight = weight; } public int from() { return v; } public int to() { return w; } public int weight() { return weight; } String toString() { return v + "->" + w; } }
4. Edge-weighted digraph: adjacency-lists implementation in Java
public class EdgeWeightedDigraph { private final int V; private final Bag<Edge>[] adj; public EdgeWeightedDigraph(int V) { this.V = V; adj = (Bag<DirectedEdge>[]) new Bag[V]; for (int v = 0; v < V; v++) adj[v] = new Bag<DirectedEdge>(); } public void addEdge(DirectedEdge e) { int v = e.from(); adj[v].add(e); } public Iterable<DirectedEdge> adj(int v) { return adj[v]; } }
5. Single-source shortest paths API
public class SP { SP(EdgeWeightedDigraph G, int s) {} //shortest paths from s in graph G double distTo(int v) {} //length of shortest path from s to v Iterable <DirectedEdge> {} //pathTo(int v) shortest path from s to v boolean hasPathTo(int v) {} //is there a path from s to v? }
6. Data structures for single-source shortest paths: Can represent the SPT with two vertex-indexed arrays:
-- distTo[v] is length of shortest path from s to v.
-- edgeTo[v] is last edge on shortest path from s to v.
7. Relax edge e = v→w.
-- distTo[v] is length of shortest known path from s to v.
-- distTo[w] is length of shortest known path from s to w.
-- edgeTo[w] is last edge on shortest known path from s to w.
-- If e = v→w gives shorter path to w through v, update both distTo[w] and edgeTo[w].
8. Shortest-paths optimality conditions:
distTo[] are the shortest path distances from s iff -->
-- For each vertex v, distTo[v] is the length of some path from s to v.
-- For each edge e = v→w, distTo[w] ≤ distTo[v] + e.weight().
Pf. ==> [necessary]
-- Suppose that distTo[w] > distTo[v] + e.weight() for some edge e = v→w.
-- Then, e gives a path from s to w (through v) of length less than distTo[w].
Pf. <== [sufficient]
-- Suppose that s = v0 → v1 → v2 → … → vk = w is a shortest path from s to w.
-- Then, distTo[w] = distTo[vk] ≤ distTo[vk-1] + ek.weight() ≤ distTo[vk-2] + ek-1.weight() + ek.weight()
≤ e1.weight() + e2.weight() + ... + ek.weight() (weight of shortest path)
-- Thus, distTo[w] is the weight of shortest path to w.
9. Generic shortest-paths algorithm:
-- Initialize distTo[s] = 0 and distTo[v] = ∞ for all other vertices.
-- Repeat until optimality conditions are satisfied:
-- Relax any edge.
Pf. of correctness
-- Throughout algorithm, distTo[v] is the length of a simple path from s to v (and edgeTo[v] is last edge on path).
-- Each successful relaxation decreases distTo[v] for some v.
-- The entry distTo[v] can decrease at most a finite number of times.
How to choose which edge to relax?
-- Dijkstra's algorithm (nonnegative weights).
-- Topological sort algorithm (no directed cycles).
-- Bellman-Ford algorithm (no negative cycles).
10. Dijkstra's algorithm:
-- Consider vertices in increasing order of distance from s
(non-tree vertex with the lowest distTo[] value).
-- Add vertex to tree and relax all edges pointing from that vertex.
Java implementation:
public class DijkstraSP { private DirectedEdge[] edgeTo; private double[] distTo; private IndexMinPQ<Double> pq; public DijkstraSP(EdgeWeightedDigraph G, int s) { edgeTo = new DirectedEdge[G.V()]; distTo = new double[G.V()]; pq = new IndexMinPQ<Double>(G.V()); for (int v = 0; v < G.V(); v++) distTo[v] = Double.POSITIVE_INFINITY; distTo[s] = 0.0; pq.insert(s, 0.0); while (!pq.isEmpty()) { int v = pq.delMin(); for (DirectedEdge e : G.adj(v)) relax(e); } } private void relax(DirectedEdge e) { int v = e.from(), w = e.to(); if (distTo[w] > distTo[v] + e.weight()) { distTo[w] = distTo[v] + e.weight(); edgeTo[w] = e; if (pq.contains(w)) pq.decreaseKey(w, distTo[w]); else pq.insert (w, distTo[w]); } } }
Running Time:
11. Main distinction between Dijkstra's and Prims algorithm : Rule used to choose next vertex for the tree:
-- Prim’s: Closest vertex to the tree (via an undirected edge).
-- Dijkstra’s: Closest vertex to the source (via a directed path).
12. Shortest paths in edge-weighted DAGs:
-- Consider vertices in topological order.
-- Relax all edges pointing from that vertex.
Java Implementation:
public class AcyclicSP { private DirectedEdge[] edgeTo; private double[] distTo; public AcyclicSP(EdgeWeightedDigraph G, int s) { edgeTo = new DirectedEdge[G.V()]; distTo = new double[G.V()]; for (int v = 0; v < G.V(); v++) distTo[v] = Double.POSITIVE_INFINITY; distTo[s] = 0.0; Topological topological = new Topological(G); for (int v : topological.order()) for (DirectedEdge e : G.adj(v)) relax(e); } }
13. Longest paths in edge-weighted DAGs:
-- Negate all weights.
-- Find shortest paths.
-- Negate weights in result.
Application: Parallel job scheduling. Given a set of jobs with durations and precedence constraints, schedule the jobs (by finding a start time for each) so as to achieve the minimum completion time, while respecting the constraints.
Solution: Critical path method -- create edge-weighted DAG:
-- Source and sink vertices.
-- Two vertices (begin and end) for each job.
-- Three edges for each job.
– begin to end (weighted by duration)
– source to begin (0 weight)
– end to sink (0 weight)
-- One edge for each precedence constraint (0 weight).
-- Use longest path from the source to schedule each job.
14. Bellman-Ford algorithm
-- Initialize distTo[s] = 0 and distTo[v] = ∞ for all other vertices.
-- Repeat V times:
- Relax each edge.
for (int i = 0; i < G.V(); i++) for (int v = 0; v < G.V(); v++) for (DirectedEdge e : G.adj(v)) relax(e);
Optimization: If distTo[v] does not change during pass i, no need to relax any edge pointing from v in pass i+1.
FIFO implementation: Maintain queue of vertices whose distTo[] changed. (Be careful to keep at most one copy of each vertex on queue.
Running Time :
Finding a negative cycle : If any vertex v is updated in phase V, there exists a negative cycle (and can trace back edgeTo[v] entries to find it).
15. Negative cycle application: arbitrage detection:
Given table of exchange rates, is there an arbitrage opportunity?
Currency exchange graph:
-- Vertex = currency.
-- Edge = transaction, with weight equal to exchange rate.
-- Find a directed cycle whose product of edge weights is > 1.
Model as a negative cycle detection problem by taking logs.
-- Let weight of edge v→w be - ln (exchange rate from currency v to w).
-- Multiplication turns to addition; > 1 turns to < 0.
-- Find a directed cycle whose sum of edge weights is < 0 (negative cycle).
16. Shortest paths summary
Dijkstra’s algorithm:
-- Nearly linear-time when weights are nonnegative.
-- Generalization encompasses DFS, BFS, and Prim.
Acyclic edge-weighted digraphs:
-- Arise in applications.
-- Faster than Dijkstra’s algorithm.
-- Negative weights are no problem.
Negative weights and negative cycles:
-- Arise in applications.
-- If no negative cycles, can find shortest paths via Bellman-Ford.
-- If negative cycles, can find one via Bellman-Ford.