Written by Hanna Haller.
Many SOI tasks are about graphs, and one of the simplest and most frequent things one wants to know about graphs is the shortest path between some pairs of vertices. You will encounter several different shortest path algorithms at SOI, but all of them solve slightly different problems. Floyd-Warshall finds the shortest path between all pairs of vertices in the graph, and the graph can have positive and negative edge weights. Breadth First Search on the other hand only works on unweighted graphs, and finds only the shortest paths from one vertex to all others. Dijkstra works on weighted graphs, but only if all edge weights are positive, and also only finds the shortest paths from one vertex to all others.
Floyd-Warshall is a typical example of an algorithm based on Dynamic Programming, but you do not need to know DP to understand Floyd-Warshall.
Idea
The basic idea is that we iteratively compute shortest paths which only use a subset of vertices as intermediate vertices, starting with the empty set and ending with the set of all vertices, meaning that we have considered all paths. The reason for computing them this way is that we can use the shortest paths from the previous iteration to easily compute the ones for the current iteration (therefore it’s DP).
In this section, we assume that there are no cycles with negative total length in the graph. Later we will explain why and what changes if we allow negative cycles.
As usual we assume that the vertices of the graph are numbered through . We keep track of the length of the shortest paths in a table , where is the length of a shortest path from to using only vertices . We initialize all to , then we set for each edge the entry to the edge weight , and for each vertex the entry to . This is because for we only consider paths without internal vertices, which only includes vertices and edges. Then we iterate for from to . For each , we consider all pairs. already contains the shortest distance from to if we only use vertices . The shortest path if we also allow as an internal vertex either does not use , so it was already considered, or it does use , in which case it consists of a shortest path from to and a shortest path from to only using vertices internally. Since we assume that there are no negative cycles, there will be such shortest - paths and - paths which do not visit internally: otherwise, we could remove this extra cycle starting and ending at , and get a path which is at least as short. So they were actually already considered for . This shows that .
Negative Cycles
Above, we assumed that there are no cycles with negative total length, and used this assumption to show that there is always a shortest path containing no vertex twice. If, on the other hand, there are negative cycles, all paths touching such a cycle can be made shorter by going once more around the cycle. Therefore, the length of such paths can be made arbitrarily negative, and the distance between their endpoints is .
It is easy to detect whether there are any negative cycles in a graph after we have run Floyd-Warshall: For each vertex on such a cycle, we will have found that there is a path of negative length from to , and therefore .
So, if there are no negative cycles, gives us the distance between each pair of vertices. Otherwise, it can tell us which vertices are in negative cycles. Most tasks do not require more than this, but it is also possible to determine the distances in the general case (that is, find for which pairs it is instead of the entry in ). This could for example be done for each pair by looping over all vertices and checking if is reachable from , is on a negative cycle, and is reachable from (reachable: if the corresponding entry in is less than ).
Running Time and Memory Usage
We have three nested loops over all vertices, and the body of the innermost loop runs in , so the running time is . The memory usage is , as we do not need to keep all versions of the distance matrix. Note that we don’t even need to keep the previous and the current version: If we only keep one version, which we overwrite during the loops over and , but also read from, this can only change entries where the shortest path contains a negative cycle (because if , visiting multiple times made the path shorter). So all the normal distances are still computed correctly, and the criterion for detecting that is in a negative cycle also still works, and nothing of relevance has changed. This leads to the following simple implementation.
Implementation
#include <iostream> //cin, cout #include <vector> //vector using namespace std; struct Edge { int from; int to; int length; }; const int INF = 1e9; vector<vector<int>> floyd_warshall(int n, const vector<Edge>& edges) { // initializing the distance matrix vector<vector<int>> d(n, vector<int>(n, INF)); for (int i=0; i<n; ++i) d[i][i] = 0; for (const auto& e: edges) d[e.from][e.to] = min(d[e.from][e.to], e.length); // iterating for (int k=0; k<n; ++k) { for (int i=0; i<n; ++i) { for (int j=0; j<n; ++j) { int newdij = d[i][k] + d[k][j]; if (newdij < d[i][j]) d[i][j] = newdij; } } } return d; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<Edge> edges; for (int i=0; i<m; ++i) { int from, to, length; cin >> from >> to >> length; edges.push_back({from, to, length}); } vector<vector<int>> d = floyd_warshall(n, edges); // .... }