Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement floyd-warshall shortest path #67

Merged
merged 2 commits into from
Feb 15, 2024
Merged
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
8 changes: 8 additions & 0 deletions path/bellman-ford.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# gograph
## Shortest Path
### Bellman-Ford
The Bellman-Ford algorithm is a graph algorithm used to find the shortest path from a source vertex to all other
vertices in a weighted graph, even in the presence of negative weight edges (as long as there are no negative weight
cycles). It was developed by Richard Bellman and Lester Ford Jr.


94 changes: 94 additions & 0 deletions path/floyd_warshall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package path

import (
"math"

"github.com/hmdsefi/gograph"
)

// FloydWarshall finds the shortest paths between all pairs of vertices in a
// weighted graph, even in the presence of negative weight edges (as long as
// there are no negative weight cycles). It was proposed by Robert Floyd and
// Stephen Warshall.
//
// Steps:
//
// 1. Initialization: Create a distance matrix D[][] where D[i][j] represents the
// shortest distance between vertex i and vertex j. Initialize this matrix with
// the weights of the edges between vertices if there is an edge, otherwise set
// the value to infinity. Also, set the diagonal elements D[i][i] to 0.
//
// 2. Shortest Path Calculation: Iterate through all vertices as intermediate vertices.
// For each pair of vertices (i, j), check if going through the current intermediate
// vertex k leads to a shorter path than the current known distance from i to j. If so,
// update the distance matrix D[i][j] to the new shorter distance D[i][k] + D[k][j].
//
// 3. Detection of Negative Cycles: After the iterations, if any diagonal element D[i][i]
// of the distance matrix is negative, it indicates the presence of a negative weight cycle
// in the graph.
//
// 4. Output: The resulting distance matrix D[][] will contain the shortest path distances
// between all pairs of vertices. If there is a negative weight cycle, it might not produce
// the correct shortest paths, but it can still detect the presence of such cycles.
//
// The time complexity of the Floyd-Warshall algorithm is O(V^3), where V is the
// number of vertices in the graph. Despite its cubic time complexity, it is often
// preferred over other algorithms like Bellman-Ford for dense graphs or when the
// graph has negative weight edges and no negative weight cycles, as it calculates
// shortest paths between all pairs of vertices in one go.
func FloydWarshall[T comparable](g gograph.Graph[T]) (map[T]map[T]float64, error) {
if !g.IsWeighted() {
return nil, ErrNotWeighted
}

Check warning on line 42 in path/floyd_warshall.go

View check run for this annotation

Codecov / codecov/patch

path/floyd_warshall.go#L41-L42

Added lines #L41 - L42 were not covered by tests

if !g.IsDirected() {
return nil, ErrNotDirected
}

Check warning on line 46 in path/floyd_warshall.go

View check run for this annotation

Codecov / codecov/patch

path/floyd_warshall.go#L45-L46

Added lines #L45 - L46 were not covered by tests

vertices := g.GetAllVertices()

dist := make(map[T]map[T]float64)
maxValue := math.Inf(1)
for _, source := range vertices {
for _, dest := range vertices {
destMap, ok := dist[source.Label()]
if !ok {
destMap = make(map[T]float64)
}

destMap[dest.Label()] = maxValue
if dest.Label() == source.Label() {
destMap[dest.Label()] = 0
}

if edge := g.GetEdge(source, dest); edge != nil {
destMap[dest.Label()] = edge.Weight()
}

dist[source.Label()] = destMap
}
}

for _, intermediate := range vertices {
for _, source := range vertices {
for _, dest := range vertices {
weight := dist[source.Label()][intermediate.Label()] + dist[intermediate.Label()][dest.Label()]
if weight < dist[source.Label()][dest.Label()] {
dist[source.Label()][dest.Label()] = weight
}
}
}
}

edges := g.AllEdges()
for _, v := range vertices {
for _, edge := range edges {
if dist[v.Label()][edge.Source().Label()] != maxValue &&
dist[v.Label()][edge.Source().Label()]+edge.Weight() < dist[v.Label()][edge.Destination().Label()] {
return nil, ErrNegativeWeightCycle
}

Check warning on line 89 in path/floyd_warshall.go

View check run for this annotation

Codecov / codecov/patch

path/floyd_warshall.go#L88-L89

Added lines #L88 - L89 were not covered by tests
}
}

return dist, nil
}
98 changes: 98 additions & 0 deletions path/floyd_warshall_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package path

import (
"errors"
"math"
"testing"

"github.com/hmdsefi/gograph"
)

func TestFloydWarshall(t *testing.T) {
g := gograph.New[string](gograph.Weighted(), gograph.Directed())

vA := g.AddVertexByLabel("A")
vB := g.AddVertexByLabel("B")
vC := g.AddVertexByLabel("C")
vD := g.AddVertexByLabel("D")
vE := g.AddVertexByLabel("E")
vF := g.AddVertexByLabel("F")

_, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5))
_, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1))
_, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(2))
_, _ = g.AddEdge(vC, vE, gograph.WithEdgeWeight(1))
_, _ = g.AddEdge(vE, vD, gograph.WithEdgeWeight(-1))
_, _ = g.AddEdge(vD, vF, gograph.WithEdgeWeight(2))
_, _ = g.AddEdge(vF, vE, gograph.WithEdgeWeight(3))

dist, err := FloydWarshall(g)
if err != nil {
t.Errorf("Expected no errors, but get an err: %s", err)
}

inf := math.Inf(1)

expectedDist := map[string]map[string]float64{
"A": {"A": 0, "B": 5, "C": 6, "D": 6, "E": 7, "F": 8},
"B": {"A": inf, "B": 0, "C": 1, "D": 1, "E": 2, "F": 3},
"C": {"A": inf, "B": inf, "C": 0, "D": 0, "E": 1, "F": 2},
"D": {"A": inf, "B": inf, "C": inf, "D": 0, "E": 5, "F": 2},
"E": {"A": inf, "B": inf, "C": inf, "D": -1, "E": 0, "F": 1},
"F": {"A": inf, "B": inf, "C": inf, "D": 2, "E": 3, "F": 0},
}

for source, destMap := range dist {
for dest, value := range destMap {
if expectedDist[source][dest] != value {
t.Fatalf(
"expected distance %f from %s to %s, but got %f",
expectedDist[source][dest],
source,
dest,
value,
)
}
}
}
}

func TestFloydWarshall_NotWeighted(t *testing.T) {
g := gograph.New[string](gograph.Directed())

vA := g.AddVertexByLabel("A")
vB := g.AddVertexByLabel("B")
vC := g.AddVertexByLabel("C")

_, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5))
_, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1))

_, err := BellmanFord(g, vA.Label())
if err == nil {
t.Errorf("Expected error, but got nil")
}

if !errors.Is(err, ErrNotWeighted) {
t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotWeighted, err)
}
}

func TestFloydWarshall_NotDirected(t *testing.T) {
g := gograph.New[string](gograph.Weighted())

vA := g.AddVertexByLabel("A")
vB := g.AddVertexByLabel("B")
vC := g.AddVertexByLabel("C")

_, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5))
_, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1))

_, err := BellmanFord(g, vA.Label())
if err == nil {
t.Errorf("Expected error, but got nil")
}

if !errors.Is(err, ErrNotDirected) {
t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotDirected, err)
}
}
Loading