The Floyd-Warshall algorithm is a classical solution to the all-pairs shortest path problem. This algorithm works on weighted graphs with both positive and negative edge weights, as long as there are no negative cycles. It finds the shortest paths between all pairs of nodes in a graph and is widely used in network routing algorithms, geographical mapping, and other applications that involve determining the most efficient paths between multiple locations.

In this article, we will explore how to implement the Floyd-Warshall algorithm in C# to not only compute the shortest distances between all pairs of vertices but also to trace the actual routes (or paths) taken for each shortest path. We will dive deep into the code with explanations and examples, followed by a conclusion summarizing the key takeaways.

Understanding the Floyd-Warshall Algorithm

The Floyd-Warshall algorithm operates on a dynamic programming principle. It builds the solution iteratively, improving the path between any two nodes by considering whether an intermediate node offers a shorter path.

Key Points of the Algorithm:

  1. Initialization: The algorithm starts by initializing the distance matrix, where the distance between a node and itself is 0, and the distance between two directly connected nodes is their edge weight. If no direct edge exists, the distance is initialized to infinity.
  2. Relaxation: For each pair of nodes, the algorithm checks if a shorter path exists by passing through another intermediate node.
  3. Tracking Paths: A separate matrix is maintained to keep track of the predecessors (or next node) for reconstructing the shortest paths.

Algorithmic Complexity:

  • Time Complexity: O(V^3), where V is the number of vertices.
  • Space Complexity: O(V^2), since it uses two matrices, one for distances and one for tracking the paths.

Step-by-Step Implementation in C#

Let’s walk through the process of implementing the Floyd-Warshall algorithm in C#.

Graph Representation

We will represent the graph using an adjacency matrix, where graph[i][j] contains the weight of the edge from node i to node j. If there is no edge between two nodes, we initialize that weight to a large number, representing infinity.

Code Structure

Let’s define the structure of our Floyd-Warshall algorithm, starting with the graph representation and initialization.

csharp

using System;

class FloydWarshall
{
private const int INF = int.MaxValue; // Used to represent infinity

public void FloydWarshallAlgorithm(int[,] graph, int verticesCount)
{
int[,] distances = new int[verticesCount, verticesCount]; // Distance matrix
int[,] next = new int[verticesCount, verticesCount]; // Path reconstruction matrix

// Initialize distance and path matrices
for (int i = 0; i < verticesCount; i++)
{
for (int j = 0; j < verticesCount; j++)
{
distances[i, j] = graph[i, j];

if (graph[i, j] != INF && i != j)
{
next[i, j] = j; // Next node in the path
}
else
{
next[i, j] = -1; // No path available initially
}
}
}

// Main logic of Floyd-Warshall algorithm
for (int k = 0; k < verticesCount; k++)
{
for (int i = 0; i < verticesCount; i++)
{
for (int j = 0; j < verticesCount; j++)
{
if (distances[i, k] != INF && distances[k, j] != INF && distances[i, k] + distances[k, j] < distances[i, j])
{
distances[i, j] = distances[i, k] + distances[k, j];
next[i, j] = next[i, k]; // Update the path to go through ‘k’
}
}
}
}

PrintSolution(distances, next, verticesCount);
}

private void PrintSolution(int[,] distances, int[,] next, int verticesCount)
{
Console.WriteLine(“Shortest distances between every pair of vertices:”);
for (int i = 0; i < verticesCount; i++)
{
for (int j = 0; j < verticesCount; j++)
{
if (distances[i, j] == INF)
{
Console.Write(“INF “);
}
else
{
Console.Write(distances[i, j] + ” “);
}
}
Console.WriteLine();
}

Console.WriteLine(“\nReconstructed paths between vertices:”);
for (int i = 0; i < verticesCount; i++)
{
for (int j = 0; j < verticesCount; j++)
{
if (i != j && next[i, j] != -1)
{
Console.Write(“Shortest path from “ + i + ” to “ + j + “: “);
PrintPath(i, j, next);
Console.WriteLine();
}
}
}
}

private void PrintPath(int u, int v, int[,] next)
{
Console.Write(u + ” “);
while (u != v)
{
u = next[u, v];
Console.Write(u + ” “);
}
}
}

class Program
{
static void Main(string[] args)
{
int[,] graph = {
{ 0, 3, INF, INF, INF, INF },
{ INF, 0, 1, INF, INF, INF },
{ INF, INF, 0, 7, INF, 2 },
{ INF, INF, INF, 0, INF, 3 },
{ INF, INF, INF, INF, 0, INF },
{ INF, INF, INF, INF, 1, 0 }
};

FloydWarshall fw = new FloydWarshall();
fw.FloydWarshallAlgorithm(graph, 6);
}
}

Explanation of Code

Initialization of Distance and Path Matrices

  • Distance matrix: We initialize it with the weights of edges between vertices. If there is no direct edge between two vertices, the value is set to infinity (INF).
  • Next matrix: This matrix is used to store the next vertex in the path from one vertex to another. It helps in reconstructing the shortest path at the end. Initially, if there is a direct edge between vertices i and j, the value is j. If no edge exists, the value is -1.

The Core of Floyd-Warshall

The main logic of the Floyd-Warshall algorithm consists of three nested loops:

  1. Loop over k: k is the intermediate vertex through which we might improve the shortest path.
  2. Loop over i and j: i and j represent the vertices for which we are currently finding the shortest path.

For each pair (i, j), we check if passing through k provides a shorter path than the currently known shortest path. If it does, we update the distance matrix and set the next node in the path as next[i, k].

Path Reconstruction

Once the algorithm has computed the shortest distances, we use the next matrix to reconstruct the paths. The method PrintPath starts from the source vertex and follows the next vertices in the path until it reaches the destination.

Example Output

For the graph provided in the code:

vbnet
Shortest distances between every pair of vertices:
0 3 INF INF INF INF
INF 0 1 INF INF INF
INF INF 0 7 INF 2
INF INF INF 0 INF 3
INF INF INF INF 0 INF
INF INF INF INF 1 0
Reconstructed paths between vertices:
Shortest path from 0 to 1: 0 1
Shortest path from 1 to 2: 1 2
Shortest path from 2 to 3: 2 5 3
Shortest path from 2 to 5: 2 5
Shortest path from 3 to 5: 3 5
Shortest path from 5 to 4: 5 4

The program prints both the shortest distances between all pairs of vertices and the paths taken for each shortest path.

Handling Negative Edge Weights and Negative Cycles

The Floyd-Warshall algorithm can handle negative edge weights but does not work correctly if there are negative cycles in the graph. A negative cycle is a cycle in the graph where the total sum of edge weights is negative. If the graph contains negative cycles, the algorithm may produce incorrect results or continue indefinitely.

To check for negative cycles, we can examine the diagonal of the distance matrix after running the algorithm. If any diagonal element is negative, it indicates a negative cycle involving that vertex.

Code for Negative Cycle Detection:

csharp
for (int i = 0; i < verticesCount; i++)
{
if (distances[i, i] < 0)
{
Console.WriteLine("Graph contains a negative cycle.");
}
}

Conclusion

The Floyd-Warshall algorithm is a powerful tool for solving the all-pairs shortest path problem in graphs. Its ability to handle both positive and negative edge weights makes it versatile for various applications. By maintaining a separate matrix to track paths, we can not only compute the shortest distances but also reconstruct the actual paths taken.

In this article, we’ve explored how to implement the algorithm in C# with clear coding examples. We started with the basics of graph representation, followed by the core logic of the Floyd-Warshall algorithm, and finally, path reconstruction. We also discussed how to handle negative cycles.

While the Floyd-Warshall algorithm is not the most efficient solution for large graphs due to its O(V^3) time complexity, it is an elegant and easy-to-implement algorithm for smaller graphs where exact results are required.