Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions graph/floyd_warshall.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
* @file
* @brief [Floyd-Warshall Algorithm
* (Floyd-Warshall All-Pairs Shortest Path)]
* (https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm)
*
* @author [Mikes Tsampounaris](https://github.com/mikestsa)
*
* @details
* Floyd-Warshall Algorithm is used to find the shortest paths between
* all pairs of vertices in a weighted graph. The algorithm works with
* both positive and negative edge weights (but no negative cycles).
*
* The algorithm uses dynamic programming approach. For each pair of
* vertices (i, j), it checks if going through an intermediate vertex k
* provides a shorter path than the current known path.
*
* Time Complexity: O(V^3) where V is the number of vertices.
* Space Complexity: O(V^2) for the distance matrix.
*
*/
#include <cassert>
#include <iostream>
#include <limits>
#include <vector>

constexpr int64_t INF = std::numeric_limits<int64_t>::max();

/**
* @namespace graph
* @brief Graph Algorithms
*/
namespace graph {

/**
* @brief Function that adds a directed edge between two vertices
*
* @param adj adjacency matrix representation of the graph
* @param u source vertex (1-indexed)
* @param v destination vertex (1-indexed)
* @param w weight of the edge
*/
void addEdge(std::vector<std::vector<int64_t>>* adj, int u, int v, int64_t w) {
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function accepts a raw pointer to a vector, but a non-const reference would be more appropriate for this use case. Using a raw pointer suggests optional or ownership semantics, neither of which apply here. Consider changing the parameter to a non-const reference instead.

Copilot uses AI. Check for mistakes.
(*adj)[u - 1][v - 1] = w;
}
Comment on lines +43 to +45
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing bounds validation for vertex indices. The function should validate that 'u' and 'v' are within valid range (1 to n where n is the matrix size) before accessing the matrix to prevent out-of-bounds access.

Copilot uses AI. Check for mistakes.

/**
* @brief Function runs the Floyd-Warshall algorithm and computes
* shortest paths between all pairs of vertices.
*
* @param adj adjacency matrix of the graph
*
* @return matrix containing shortest distances between all pairs
*/
std::vector<std::vector<int64_t>> floydWarshall(
std::vector<std::vector<int64_t>>* adj) {
/// n denotes the number of vertices in graph
size_t n = adj->size();

/// copy the adjacency matrix to dist
std::vector<std::vector<int64_t>> dist = *adj;
Comment on lines +56 to +61
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function accepts a raw pointer to a vector, but a const reference would be more appropriate for this use case. Using a raw pointer suggests optional or ownership semantics, neither of which apply here. Since the function only reads the input matrix, consider changing the parameter to a const reference instead.

Suggested change
std::vector<std::vector<int64_t>>* adj) {
/// n denotes the number of vertices in graph
size_t n = adj->size();
/// copy the adjacency matrix to dist
std::vector<std::vector<int64_t>> dist = *adj;
const std::vector<std::vector<int64_t>>& adj) {
/// n denotes the number of vertices in graph
size_t n = adj.size();
/// copy the adjacency matrix to dist
std::vector<std::vector<int64_t>> dist = adj;

Copilot uses AI. Check for mistakes.

/// for each intermediate vertex k
for (int k = 0; k < n; k++) {
/// for each source vertex i
for (int i = 0; i < n; i++) {
/// for each destination vertex j
for (int j = 0; j < n; j++) {
Comment on lines +64 to +68
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch between loop variable and container size. The variable 'i' is declared as 'int' but is being compared with 'n' which is 'size_t'. This can cause signed/unsigned comparison warnings. The loop variable should be 'size_t' to match the type of 'n'.

Suggested change
for (int k = 0; k < n; k++) {
/// for each source vertex i
for (int i = 0; i < n; i++) {
/// for each destination vertex j
for (int j = 0; j < n; j++) {
for (size_t k = 0; k < n; k++) {
/// for each source vertex i
for (size_t i = 0; i < n; i++) {
/// for each destination vertex j
for (size_t j = 0; j < n; j++) {

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +68
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch between loop variable and container size. The variable 'k' is declared as 'int' but is being compared with 'n' which is 'size_t'. This can cause signed/unsigned comparison warnings. The loop variable should be 'size_t' to match the type of 'n'.

Suggested change
for (int k = 0; k < n; k++) {
/// for each source vertex i
for (int i = 0; i < n; i++) {
/// for each destination vertex j
for (int j = 0; j < n; j++) {
for (size_t k = 0; k < n; k++) {
/// for each source vertex i
for (size_t i = 0; i < n; i++) {
/// for each destination vertex j
for (size_t j = 0; j < n; j++) {

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +68
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch between loop variable and container size. The variable 'j' is declared as 'int' but is being compared with 'n' which is 'size_t'. This can cause signed/unsigned comparison warnings. The loop variable should be 'size_t' to match the type of 'n'.

Suggested change
for (int k = 0; k < n; k++) {
/// for each source vertex i
for (int i = 0; i < n; i++) {
/// for each destination vertex j
for (int j = 0; j < n; j++) {
for (size_t k = 0; k < n; k++) {
/// for each source vertex i
for (size_t i = 0; i < n; i++) {
/// for each destination vertex j
for (size_t j = 0; j < n; j++) {

Copilot uses AI. Check for mistakes.
/// if path through k is shorter, update distance
if (dist[i][k] != INF && dist[k][j] != INF) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
Comment on lines +70 to +72
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential integer overflow when adding edge weights. The expression 'dist[i][k] + dist[k][j]' can overflow before the comparison with 'dist[i][j]' is performed, leading to incorrect shortest path calculations. Consider checking if the addition would overflow before performing it, or restructure the comparison to avoid addition.

Copilot uses AI. Check for mistakes.
}
}
}
}
}

return dist;
}

/**
* @brief Function to check if the graph contains a negative cycle
*
* @param dist the distance matrix after running Floyd-Warshall
*
* @return true if negative cycle exists, false otherwise
*/
bool hasNegativeCycle(const std::vector<std::vector<int64_t>>& dist) {
size_t n = dist.size();
for (size_t i = 0; i < n; i++) {
if (dist[i][i] < 0) {
return true;
}
}
return false;
}

/**
* @brief Function to get shortest path distance between two vertices
*
* @param dist the distance matrix
* @param s source vertex (0-indexed)
* @param t target vertex (0-indexed)
*
* @return shortest distance if target is reachable from source else -1
*/
int64_t getDistance(const std::vector<std::vector<int64_t>>& dist, int s,
int t) {
if (dist[s][t] != INF) {
return dist[s][t];
}
Comment on lines +110 to +112
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing bounds validation for vertex indices. The function should validate that 's' and 't' are within valid range (0 to n-1 where n is the matrix size) before accessing the matrix to prevent out-of-bounds access.

Suggested change
if (dist[s][t] != INF) {
return dist[s][t];
}
// Validate indices to prevent out-of-bounds access
if (s < 0 || t < 0) {
return -1;
}
const size_t n = dist.size();
if (n == 0) {
return -1;
}
const size_t rows = n;
const size_t cols = dist[0].size();
const size_t si = static_cast<size_t>(s);
const size_t ti = static_cast<size_t>(t);
if (si >= rows || ti >= cols) {
return -1;
}
if (dist[si][ti] != INF) {
return dist[si][ti];
}

Copilot uses AI. Check for mistakes.
return -1;
}

} // namespace graph

/** Function to test the Algorithm */
void tests() {
std::cout << "Initiating Predefined Tests..." << std::endl;
std::cout << "Initiating Test 1..." << std::endl;

std::vector<std::vector<int64_t>> adj1(4, std::vector<int64_t>(4, INF));
for (int i = 0; i < 4; i++) {
adj1[i][i] = 0;
}
Comment on lines +118 to +126
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated matrix initialization pattern. The pattern of creating a matrix filled with INF and then setting diagonal elements to 0 appears in multiple test cases. Consider extracting this into a helper function to improve maintainability and reduce code duplication.

Suggested change
/** Function to test the Algorithm */
void tests() {
std::cout << "Initiating Predefined Tests..." << std::endl;
std::cout << "Initiating Test 1..." << std::endl;
std::vector<std::vector<int64_t>> adj1(4, std::vector<int64_t>(4, INF));
for (int i = 0; i < 4; i++) {
adj1[i][i] = 0;
}
/**
* @brief Helper function to create an n x n matrix initialized with INF
* and 0 on the diagonal.
*
* @param n number of vertices (matrix dimension)
* @return initialized adjacency matrix
*/
static std::vector<std::vector<int64_t>> createInitializedMatrix(
std::size_t n) {
std::vector<std::vector<int64_t>> matrix(
n, std::vector<int64_t>(n, INF));
for (std::size_t i = 0; i < n; i++) {
matrix[i][i] = 0;
}
return matrix;
}
/** Function to test the Algorithm */
void tests() {
std::cout << "Initiating Predefined Tests..." << std::endl;
std::cout << "Initiating Test 1..." << std::endl;
std::vector<std::vector<int64_t>> adj1 = createInitializedMatrix(4);

Copilot uses AI. Check for mistakes.
graph::addEdge(&adj1, 1, 2, 3);
graph::addEdge(&adj1, 1, 4, 5);
graph::addEdge(&adj1, 2, 1, 2);
graph::addEdge(&adj1, 2, 4, 4);
graph::addEdge(&adj1, 3, 2, 1);
graph::addEdge(&adj1, 4, 3, 2);

auto dist1 = graph::floydWarshall(&adj1);

assert(graph::getDistance(dist1, 0, 2) == 7); // 1 -> 4 -> 3 = 5 + 2 = 7
std::cout << "Test 1 Passed..." << std::endl;

std::cout << "Initiating Test 2..." << std::endl;
assert(graph::getDistance(dist1, 2, 0) == 3); // 3 -> 2 -> 1 = 1 + 2 = 3
std::cout << "Test 2 Passed..." << std::endl;

std::cout << "Initiating Test 3..." << std::endl;
std::vector<std::vector<int64_t>> adj2(4, std::vector<int64_t>(4, INF));
for (int i = 0; i < 4; i++) {
adj2[i][i] = 0;
}
graph::addEdge(&adj2, 1, 2, 4);
graph::addEdge(&adj2, 2, 3, -2);
graph::addEdge(&adj2, 3, 4, 3);

auto dist2 = graph::floydWarshall(&adj2);

assert(graph::getDistance(dist2, 0, 2) == 2); // 1 -> 2 -> 3 = 4 + (-2) = 2
assert(graph::getDistance(dist2, 0, 3) ==
5); // 1 -> 2 -> 3 -> 4 = 4 + (-2) + 3 = 5
assert(!graph::hasNegativeCycle(dist2));
std::cout << "Test 3 Passed..." << std::endl;

std::cout << "Initiating Test 4..." << std::endl;
std::vector<std::vector<int64_t>> adj3(1, std::vector<int64_t>(1, 0));
auto dist3 = graph::floydWarshall(&adj3);
assert(graph::getDistance(dist3, 0, 0) == 0);
std::cout << "Test 4 Passed..." << std::endl;

std::cout << "All Tests Passed..." << std::endl << std::endl;
}

/** Main function */
int main() {
// running predefined tests
tests();

int vertices = int(), edges = int();
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary explicit initialization with default constructor. The expression 'int()' is verbose and can simply be '0' or the variable can be left uninitialized since it's assigned before use. The same applies to the edges variable on this line.

Copilot uses AI. Check for mistakes.
std::cout << "Enter the number of vertices : ";
std::cin >> vertices;
std::cout << "Enter the number of edges : ";
std::cin >> edges;

std::vector<std::vector<int64_t>> adj(vertices,
std::vector<int64_t>(vertices, INF));
for (int i = 0; i < vertices; i++) {
adj[i][i] = 0;
}

int u = int(), v = int();
int64_t w = int64_t();
Comment on lines +186 to +187
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary explicit initialization with default constructor. The expressions 'int()' and 'int64_t()' are verbose. These variables can be declared without initialization since they are assigned values from user input before use.

Copilot uses AI. Check for mistakes.
std::cout << "Enter edges (u v weight):" << std::endl;
while (edges--) {
std::cin >> u >> v >> w;
graph::addEdge(&adj, u, v, w);
}

auto dist = graph::floydWarshall(&adj);

if (graph::hasNegativeCycle(dist)) {
std::cout << "Graph contains a negative cycle!" << std::endl;
} else {
int s = int(), t = int();
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary explicit initialization with default constructor. The expressions 'int()' are verbose. These variables can be declared without initialization since they are assigned values from user input before use.

Copilot uses AI. Check for mistakes.
std::cout << "Enter source and target vertices : ";
std::cin >> s >> t;
int64_t result = graph::getDistance(dist, s - 1, t - 1);
if (result == -1) {
std::cout << "Target not reachable from source" << std::endl;
} else {
std::cout << "Shortest Path Distance : " << result << std::endl;
}
}
return 0;
}
Loading