Shortest Paths
Shortest-path algorithms find minimum-cost routes through weighted graphs. They are related to traversal, but weights change the problem. BFS is enough when every edge has equal cost; once edges have different nonnegative weights, Dijkstra's algorithm is the standard single-source method. For all-pairs shortest paths, Floyd-Warshall uses dynamic programming over an adjacency matrix.
The source textbook's graph chapter treats shortest paths after spanning trees. That contrast is important. A minimum spanning tree minimizes the total weight of a connecting tree, while shortest paths minimize route cost between specified vertices. The MST of a road network does not necessarily contain the shortest route between two cities.
Definitions
Let be a weighted graph with edge weight function . The length of a path is the sum of its edge weights. A shortest path from source s to vertex v is a path with minimum total weight among all paths from s to v.
The single-source shortest-path problem computes distances:
for every vertex v, where is the true shortest-path distance from s to v.
The all-pairs shortest-path problem computes for every ordered pair of vertices.
Core operation:
- Relaxation: given an edge
u -> v, if the known distance tovcan be improved by going throughu, update it:
Dijkstra's algorithm assumes all edge weights are nonnegative. Floyd-Warshall can handle negative edges, but not negative cycles. A negative cycle makes shortest paths undefined for reachable pairs because the path cost can be reduced without bound.
Key results
Dijkstra's algorithm maintains a set of finalized vertices whose distances are known to be optimal. At each step it selects the unsettled vertex with the smallest tentative distance. With nonnegative weights, no later path through another unsettled vertex can improve it, because every extra edge adds a nonnegative amount.
Using an adjacency matrix and scanning for the next minimum tentative distance gives time. Using adjacency lists and a binary heap priority queue gives in a typical implementation.
Floyd-Warshall uses the recurrence:
Here is the best distance from i to j using only intermediate vertices from the set {0, 1, ..., k}. The final matrix after all k values gives all-pairs shortest paths. The time complexity is and the matrix space is .
| Algorithm | Problem | Weight restrictions | Time with common representation | Main data structure |
|---|---|---|---|---|
| BFS | single-source unweighted | all edges equal | queue | |
| Dijkstra | single-source weighted | nonnegative weights | matrix or heap | priority queue or arrays |
| Floyd-Warshall | all-pairs weighted | no negative cycles | distance matrix |
Shortest-path implementations usually store a predecessor or parent array in addition to distances. Distances answer "how much does the best path cost?" Parents answer "what is the path?" After Dijkstra finishes, following parent[target], then parent[parent[target]], and so on reconstructs the path backward from the target to the source. If the parent remains -1 for a non-source vertex, the vertex was unreachable.
The choice between Dijkstra and Floyd-Warshall is mostly about query pattern and graph size. If there is one source or a few sources in a sparse graph, repeated Dijkstra with adjacency lists is usually appropriate. If all vertex pairs are needed and the graph is small enough for a matrix, Floyd-Warshall is compact and handles dense graphs well. Its triple loop is also a classic example of dynamic programming over allowed intermediate vertices.
Negative weights require special care. A single negative edge can invalidate Dijkstra's finalized-distance argument. Floyd-Warshall can incorporate negative edges, but a negative diagonal entry after the algorithm indicates a negative cycle reachable from that vertex.
Visual
Relaxation flow:
Worked example 1: Dijkstra from A
Problem: Run Dijkstra's algorithm from A on the graph in the visual.
Method: keep tentative distances. Use infinity for unknown distances. Choose the unsettled vertex with smallest tentative distance each round.
Initial:
d[A]=0, d[B]=inf, d[C]=inf, d[D]=inf, d[E]=inf
settled = {}
- Choose
Awith distance0. Relax outgoing edges:A -> Bweight2:0 + 2 < inf, sod[B]=2, parentA.A -> Cweight5:0 + 5 < inf, sod[C]=5, parentA. Settled:{A}.
- Choose unsettled minimum
Bwith distance2. Relax:B -> Cweight1:2 + 1 = 3 < 5, sod[C]=3, parentB.B -> Dweight2:2 + 2 = 4 < inf, sod[D]=4, parentB. Settled:{A,B}.
- Choose
Cwith distance3. Relax:C -> Dweight3:3 + 3 = 6, not better than4.C -> Eweight5:3 + 5 = 8 < inf, sod[E]=8, parentC. Settled:{A,B,C}.
- Choose
Dwith distance4. Relax:D -> Eweight1:4 + 1 = 5 < 8, sod[E]=5, parentD. Settled:{A,B,C,D}.
- Choose
Ewith distance5. No outgoing improvements. Done.
Checked answer: distances are A:0, B:2, C:3, D:4, E:5. The shortest path to E is A -> B -> D -> E, with cost 2 + 2 + 1 = 5.
Worked example 2: one Floyd-Warshall update
Problem: Suppose vertices are 0, 1, 2, and the current distance matrix before allowing vertex 1 as an intermediate is:
0 1 2
0 0 4 10
1 inf 0 3
2 inf inf 0
Update distances using vertex 1 as a possible intermediate.
Method: for every pair (i,j), test whether D[i][1] + D[1][j] improves D[i][j].
- Pair
(0,2):
Current D[0][2] is 10, so update it to 7.
- Pair
(0,0):D[0][1] + D[1][0] = 4 + inf = inf, no improvement over0. - Pair
(0,1):D[0][1] + D[1][1] = 4 + 0 = 4, equal to current4. - Pairs starting from
1: going through1addsD[1][1] = 0, so no improvement. - Pairs starting from
2:D[2][1] = inf, so no path through1.
Updated matrix:
0 1 2
0 0 4 7
1 inf 0 3
2 inf inf 0
Checked answer: the new shortest known path from 0 to 2 is 0 -> 1 -> 2 with cost 7, replacing the direct edge cost 10.
Code
This C program implements Dijkstra's algorithm with an adjacency matrix and simple minimum selection.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define V 5
#define INF 1000000000
static int min_unvisited(int dist[V], int used[V]) {
int best = -1;
for (int i = 0; i < V; ++i) {
if (!used[i] && (best == -1 || dist[i] < dist[best])) {
best = i;
}
}
return best;
}
static void dijkstra(int graph[V][V], int source) {
int dist[V];
int parent[V];
int used[V] = {0};
for (int i = 0; i < V; ++i) {
dist[i] = INF;
parent[i] = -1;
}
dist[source] = 0;
for (int step = 0; step < V; ++step) {
int u = min_unvisited(dist, used);
if (u == -1 || dist[u] == INF) break;
used[u] = 1;
for (int v = 0; v < V; ++v) {
if (graph[u][v] != INF && !used[v]) {
int candidate = dist[u] + graph[u][v];
if (candidate < dist[v]) {
dist[v] = candidate;
parent[v] = u;
}
}
}
}
for (int i = 0; i < V; ++i) {
printf("vertex %d: dist=%d parent=%d\n", i, dist[i], parent[i]);
}
}
int main(void) {
int g[V][V] = {
{INF, 2, 5, INF, INF},
{INF, INF, 1, 2, INF},
{INF, INF, INF, 3, 5},
{INF, INF, INF, INF, 1},
{INF, INF, INF, INF, INF}
};
dijkstra(g, 0);
return EXIT_SUCCESS;
}
Common pitfalls
- Running Dijkstra on graphs with negative edge weights. The finalized-distance argument no longer works.
- Confusing unreachable distance with a large real distance. Choose
INFcarefully to avoid overflow indist[u] + weight. - Forgetting to initialize
dist[source] = 0. - Treating an MST as a shortest-path tree. The optimization goals differ.
- Using Floyd-Warshall on a graph with a negative cycle and interpreting the output as finite shortest paths.
- In an adjacency matrix, using
0for "no edge" when zero-weight edges are possible.