Skip to content
75 changes: 75 additions & 0 deletions Algorithms.Tests/Graph/BellmanFordTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Algorithms.Graph;
using DataStructures.Graph;
using NUnit.Framework;
using FluentAssertions;
using System.Collections.Generic;
using System;

namespace Algorithms.Tests.Graph
{
public class BellmanFordTests
{
[Test]
public void CorrectDistancesTest()
{
var graph = new DirectedWeightedGraph<int>(10);

var vertex1 = graph.AddVertex(1);
var vertex2 = graph.AddVertex(2);
var vertex3 = graph.AddVertex(3);
var vertex4 = graph.AddVertex(4);
var vertex5 = graph.AddVertex(5);

graph.AddEdge(vertex1, vertex2, 3);
graph.AddEdge(vertex1, vertex5, -4);
graph.AddEdge(vertex1, vertex3, 8);
graph.AddEdge(vertex2, vertex5, 7);
graph.AddEdge(vertex2, vertex4, 1);
graph.AddEdge(vertex3, vertex2, 4);
graph.AddEdge(vertex4, vertex3, -5);
graph.AddEdge(vertex4, vertex1, 2);
graph.AddEdge(vertex5, vertex4, 6);

var expectedDistances = new Dictionary<Vertex<int>, double>
{
{ vertex1, 0 },
{ vertex2, 1 },
{ vertex3, -3 },
{ vertex4, 2 },
{ vertex5, -4 }
};

var bellmanFord = new BellmanFord<int>(graph, new Dictionary<Vertex<int>, double>(), new Dictionary<Vertex<int>, Vertex<int>?>());

var calculatedDistances = bellmanFord.Run(vertex1);

foreach (var vertex in graph.Vertices)
{
if (vertex != null)
{
calculatedDistances[vertex].Should().BeApproximately(expectedDistances[vertex], 0.001);
}
}
}

[Test]
public void NegativeWeightCycleTest()
{
var graph = new DirectedWeightedGraph<int>(3);

var vertex1 = graph.AddVertex(1);
var vertex2 = graph.AddVertex(2);
var vertex3 = graph.AddVertex(3);

graph.AddEdge(vertex1, vertex2, -1);
graph.AddEdge(vertex2, vertex3, -2);
graph.AddEdge(vertex3, vertex1, -3);

var bellmanFord = new BellmanFord<int>(graph, new Dictionary<Vertex<int>, double>(), new Dictionary<Vertex<int>, Vertex<int>?>());

Action action = () => bellmanFord.Run(vertex1);

action.Should().Throw<InvalidOperationException>().WithMessage("Graph contains a negative weight cycle.");
}
}
}
120 changes: 120 additions & 0 deletions Algorithms/Graph/BellmanFord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using DataStructures.Graph;

namespace Algorithms.Graph
{
/// <summary>
/// Bellman-Ford algorithm on directed weighted graph.
/// </summary>
/// <typeparam name="T">Generic type of data in the graph.</typeparam>
public class BellmanFord<T>
{
private readonly DirectedWeightedGraph<T> graph;
private readonly Dictionary<Vertex<T>, double> distances;
private readonly Dictionary<Vertex<T>, Vertex<T>?> predecessors;

public BellmanFord(DirectedWeightedGraph<T> graph, Dictionary<Vertex<T>, double> distances, Dictionary<Vertex<T>, Vertex<T>?> predecessors)
{
this.graph = graph;
this.distances = distances;
this.predecessors = predecessors;
}

/// <summary>
/// Runs the Bellman-Ford algorithm to find the shortest distances from the source vertex to all other vertices.
/// </summary>
/// <param name="sourceVertex">Source vertex for shortest path calculation.</param>
/// <returns>
/// A dictionary containing the shortest distances from the source vertex to all other vertices.
/// If a vertex is unreachable from the source, it will have a value of double.PositiveInfinity.
/// </returns>
public Dictionary<Vertex<T>, double> Run(Vertex<T> sourceVertex)
{
InitializeDistances(sourceVertex);
RelaxEdges();
CheckForNegativeCycles();
return distances;
}

private void InitializeDistances(Vertex<T> sourceVertex)
{
foreach (var vertex in graph.Vertices)
{
if (vertex != null)
{
distances[vertex] = double.PositiveInfinity;
predecessors[vertex] = null;
}
}

distances[sourceVertex] = 0;
}

private void RelaxEdges()
{
int vertexCount = graph.Count;

for (int i = 0; i < vertexCount - 1; i++)
{
foreach (var vertex in graph.Vertices)
{
if (vertex != null)
{
RelaxEdgesForVertex(vertex);
}
}
}
}

private void RelaxEdgesForVertex(Vertex<T> u)
{
foreach (var neighbor in graph.GetNeighbors(u))
{
if (neighbor == null)
{
continue;
}

var v = neighbor;
var weight = graph.AdjacentDistance(u, v);

if (distances[u] + weight < distances[v])
{
distances[v] = distances[u] + weight;
predecessors[v] = u;
}
}
}

private void CheckForNegativeCycles()
{
foreach (var vertex in graph.Vertices)
{
if (vertex != null)
{
CheckForNegativeCyclesForVertex(vertex);
}
}
}

private void CheckForNegativeCyclesForVertex(Vertex<T> u)
{
foreach (var neighbor in graph.GetNeighbors(u))
{
if (neighbor == null)
{
continue;
}

var v = neighbor;
var weight = graph.AdjacentDistance(u, v);

if (distances[u] + weight < distances[v])
{
throw new InvalidOperationException("Graph contains a negative weight cycle.");
}
}
}
}
}