Worldscope

Floyd-Warshall Algorithm

Palavras-chave:

Publicado em: 31/08/2025

Floyd-Warshall Algorithm: Finding All-Pairs Shortest Paths

The Floyd-Warshall algorithm is a dynamic programming approach used to find the shortest paths between all pairs of vertices in a weighted graph. Unlike Dijkstra's algorithm, which finds shortest paths from a single source vertex, Floyd-Warshall computes shortest paths between *every* pair of vertices in a single run. This article will explain the algorithm, its implementation, and its analysis, along with alternatives.

Fundamental Concepts / Prerequisites

To understand the Floyd-Warshall algorithm, you should have a basic understanding of:

  • Graphs: Concepts like vertices, edges, weighted edges, directed and undirected graphs.
  • Adjacency Matrix: A representation of a graph using a matrix, where the element at (i, j) represents the weight of the edge between vertex i and vertex j (or infinity if no edge exists).
  • Dynamic Programming: The idea of breaking down a complex problem into smaller overlapping subproblems, solving them, and storing their solutions to avoid recomputation.

Core Implementation in Python


def floyd_warshall(graph):
    """
    Implements the Floyd-Warshall algorithm to find the shortest paths between all pairs of vertices in a graph.

    Args:
        graph: A 2D list representing the adjacency matrix of the graph.
               graph[i][j] represents the weight of the edge between vertex i and vertex j.
               If there is no edge between i and j, graph[i][j] should be float('inf').
               The diagonal elements graph[i][i] should be 0.

    Returns:
        A 2D list representing the shortest path distances between all pairs of vertices.
        Returns None if the graph contains negative cycles.
    """
    n = len(graph)
    dist = [row[:] for row in graph]  # Create a copy of the graph

    # Iterate through all possible intermediate vertices
    for k in range(n):
        # Iterate through all possible source vertices
        for i in range(n):
            # Iterate through all possible destination vertices
            for j in range(n):
                # If vertex k is on a shorter path from i to j, update the distance
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

    # Check for negative cycles
    for i in range(n):
        if dist[i][i] < 0:
            print("Negative cycle detected!")
            return None

    return dist

# Example usage:
graph = [
    [0, 3, float('inf'), 7],
    [8, 0, 2, float('inf')],
    [5, float('inf'), 0, 1],
    [2, float('inf'), float('inf'), 0]
]

shortest_paths = floyd_warshall(graph)

if shortest_paths:
    print("Shortest path distances between all pairs of vertices:")
    for row in shortest_paths:
        print(row)

Code Explanation

1. **Initialization:** The `floyd_warshall(graph)` function takes the adjacency matrix `graph` as input. A copy of the graph, named `dist`, is created. This copy will store the intermediate shortest path distances. This avoids modifying the original graph.

2. **Triple Nested Loop:** The algorithm uses three nested loops.

  • The outermost loop iterates through all vertices `k` from 0 to `n-1`. `k` represents the intermediate vertex we are considering.
  • The middle loop iterates through all vertices `i` from 0 to `n-1`. `i` represents the source vertex.
  • The innermost loop iterates through all vertices `j` from 0 to `n-1`. `j` represents the destination vertex.

3. **Distance Update:** Inside the innermost loop, the core dynamic programming step occurs: `dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])`. This line checks if going from vertex `i` to vertex `j` via vertex `k` results in a shorter path than the current shortest path between `i` and `j`. If it does, the `dist[i][j]` is updated.

4. **Negative Cycle Detection:** After the loops complete, the algorithm checks for negative cycles. A negative cycle is a cycle in the graph where the sum of the edge weights is negative. If a negative cycle exists, the shortest path between some vertices is undefined (because you can keep going around the cycle to reduce the path length). The code checks for this by examining the diagonal elements of the `dist` matrix. If any `dist[i][i]` is negative, it indicates the presence of a negative cycle, and the function returns `None`. A negative diagonal entry means that going from a vertex back to itself has a negative cost, which can only happen with a negative cycle.

5. **Return Value:** If no negative cycles are detected, the function returns the `dist` matrix, which now contains the shortest path distances between all pairs of vertices.

Complexity Analysis

The Floyd-Warshall algorithm has the following complexity:

  • Time Complexity: O(V3), where V is the number of vertices in the graph. This is due to the three nested loops in the core of the algorithm.
  • Space Complexity: O(V2), where V is the number of vertices in the graph. This is because we need to store the distance matrix, which is a V x V matrix. The algorithm works in place (after the initial copy), so no additional major data structures are required.

Alternative Approaches

An alternative approach to finding all-pairs shortest paths is to use Dijkstra's algorithm (or Bellman-Ford algorithm for graphs with negative edge weights) from each vertex as a source. For a graph with V vertices, running Dijkstra's algorithm V times would have a time complexity of O(V * E log V) (using a min-priority queue) or O(V3) using an adjacency matrix, where E is the number of edges. If the graph is dense (E is close to V2), then the performance of this approach could be comparable to Floyd-Warshall. Bellman-Ford has time complexity O(V*E) and would be O(V^3) when run for all vertices, but would also detect negative cycles. The choice of algorithm depends on the specific graph properties and the need to detect negative cycles.

Conclusion

The Floyd-Warshall algorithm provides a relatively simple and effective way to compute all-pairs shortest paths in a weighted graph. Its cubic time complexity makes it suitable for graphs with a moderate number of vertices. Its ability to detect negative cycles is a valuable feature. However, for sparse graphs or when only single-source shortest paths are needed, Dijkstra's algorithm (or Bellman-Ford when negative edges exist) may be more efficient. DAA Course.