Graph Algorithms contents
Floyd-Warshall Algorithm
All-pairs shortest paths in O(n³) with three nested loops, path reconstruction, negative cycles, and a much faster Python formulation.
Read first: Graphs: Terminology and Representation
Given a weighted graph with vertices (weights may be negative, but there must be no negative cycle), the Floyd-Warshall algorithm finds the shortest distance between every pair of vertices in time and memory. It is only a few lines long.
Algorithm
It's dynamic programming over the set of allowed intermediate vertices. Let be the shortest path from to using only vertices as intermediates. A shortest path either avoids vertex or goes through it once:
It is safe to update a single matrix in place, because row and column do not change during iteration (when there are no negative cycles).
INF = float("inf")
def floyd_warshall(n, edges):
d = [[0 if i == j else INF for j in range(n)] for i in range(n)]
for u, v, w in edges:
if w < d[u][v]:
d[u][v] = w # keep the smallest of parallel edges
for k in range(n):
row_k = d[k]
for row_i in d:
d_ik = row_i[k]
if d_ik == INF:
continue # i cannot reach k: nothing to improve
for j in range(n):
through_k = d_ik + row_k[j]
if through_k < row_i[j]:
row_i[j] = through_k
return d
edges = [(0, 1, 3), (1, 2, 1), (0, 2, 7), (2, 3, 2), (3, 0, 4)]
d = floyd_warshall(4, edges)
assert d[0] == [0, 3, 4, 6]
assert d[3] == [4, 7, 8, 0]
assert d[2][1] == 2 + 4 + 3 # 2 -> 3 -> 0 -> 1The order of the loops matters: k must be the outermost loop.
How fast is it in Python?
The algorithm does steps of the innermost loop. We measured this implementation on random complete graphs:
| time | |
|---|---|
| 200 | 0.33 s |
| 300 | 1.2 s |
| 400 | 3.0 s |
So is comfortable and is about the limit for a 5-second judge. Hoisting row_k and row_i out of the inner loop (as above) is worth 10-20%; nothing else in pure Python helps much.
If numpy is available the update is a single vectorized line and handles : d = np.minimum(d, d[:, k, None] + d[None, k, :]). Many judges (including this platform's sandbox) provide only the standard library, so treat this as a note for your own projects.
If only some sources matter, or the graph is sparse, running Dijkstra from each source () can beat Floyd-Warshall by far.
Restoring paths
Keep, for each pair, the next vertex on a shortest path from to , and update it together with the distance:
def floyd_with_paths(n, edges):
d = [[0 if i == j else INF for j in range(n)] for i in range(n)]
nxt = [[None] * n for _ in range(n)]
for u, v, w in edges:
if w < d[u][v]:
d[u][v] = w
nxt[u][v] = v
for i in range(n):
nxt[i][i] = i
for k in range(n):
for i in range(n):
for j in range(n):
if d[i][k] + d[k][j] < d[i][j]:
d[i][j] = d[i][k] + d[k][j]
nxt[i][j] = nxt[i][k]
return d, nxt
def path(nxt, i, j):
if nxt[i][j] is None:
return None
out = [i]
while i != j:
i = nxt[i][j]
out.append(i)
return out
dist, nxt = floyd_with_paths(4, edges)
assert path(nxt, 2, 1) == [2, 3, 0, 1]
assert path(nxt, 0, 3) == [0, 1, 2, 3]Negative cycles
If the graph has a negative cycle, some d[v][v] becomes negative. Vertices , have a "shortest path" of when there is a vertex with d[t][t] < 0 that is reachable from and can reach :
def has_negative_cycle(d):
return any(d[v][v] < 0 for v in range(len(d)))
bad = floyd_warshall(3, [(0, 1, 1), (1, 2, -3), (2, 0, 1)]) # cycle weight -1
assert has_negative_cycle(bad)
assert not has_negative_cycle(d)Testing
Compare with Dijkstra from every source on random non-negative graphs:
import heapq, random
def dijkstra_all(n, edges):
adj = [[] for _ in range(n)]
for u, v, w in edges:
adj[u].append((v, w))
result = []
for s in range(n):
dist = [INF] * n
dist[s] = 0
heap = [(0, s)]
while heap:
dd, v = heapq.heappop(heap)
if dd > dist[v]:
continue
for u, w in adj[v]:
if dd + w < dist[u]:
dist[u] = dd + w
heapq.heappush(heap, (dd + w, u))
result.append(dist)
return result
random.seed(9)
for _ in range(200):
n = random.randint(1, 9)
es = [(random.randrange(n), random.randrange(n), random.randint(0, 9)) for _ in range(random.randint(0, 25))]
expected = dijkstra_all(n, es)
assert floyd_warshall(n, es) == expectedOther uses
- Transitive closure (which vertices can reach which): replace
min/+byor/and. - Graph diameter, radius and centres.
- Minimum cycle through a vertex and shortest cycles in small graphs.
- Reachability with big bitsets: in Python, encode each row as an integer and use
|, which reaches easily:
def transitive_closure(n, edges):
reach = [1 << i for i in range(n)] # bitmask of vertices reachable from i
for u, v in edges:
reach[u] |= 1 << v
for k in range(n):
bit = 1 << k
for i in range(n):
if reach[i] & bit:
reach[i] |= reach[k]
return reach
r = transitive_closure(4, [(0, 1), (1, 2), (3, 0)])
assert [bin(x).count("1") for x in r] == [3, 2, 1, 4]
assert (r[3] >> 2) & 1Practice problems
- UVA: Page Hopping
- SPOJ: Possible Friends
- CODEFORCES: Greg and Graph
- SPOJ: CHICAGO - 106 miles to Chicago
- UVA 10724 - Road Construction
- UVA 117 - The Postal Worker Rings Once
- Codeforces - Traveling Graph
- UVA - 1198 - The Geodetic Set Problem
- UVA - 10048 - Audiophobia
- UVA - 125 - Numbering Paths
- LOJ - Travel Company
- UVA 423 - MPI Maelstrom
- UVA 1416 - Warfare And Logistics
- UVA 1233 - USHER
- UVA 10793 - The Orc Attack
- UVA 10099 The Tourist Guide
- UVA 869 - Airline Comparison
- UVA 13211 - Geonosis
- SPOJ - Defend the Rohan
- Codeforces - Roads in Berland
- Codeforces - String Problem
- GYM - Manic Moving (C)
- SPOJ - Arbitrage
- UVA - 12179 - Randomly-priced Tickets
- LOJ - 1086 - Jogging Trails
- SPOJ - Ingredients
- CSES - Shortest Routes II