From f3cb61d346be306f7d153e57262d8cf2592564e3 Mon Sep 17 00:00:00 2001 From: "Artur M. Wolff" Date: Wed, 30 Oct 2019 22:02:17 +0100 Subject: [PATCH 1/2] Make ShortestPath faster --- path.go | 51 ++++++++++++++++++++++++++++++---------- path_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/path.go b/path.go index 850ddec..deafc11 100644 --- a/path.go +++ b/path.go @@ -4,21 +4,48 @@ package graph // Only edges with non-negative costs are included. // The number dist is the length of the path, or -1 if w cannot be reached. // -// The time complexity is O((|E| + |V|)⋅log|V|), where |E| is the number of edges -// and |V| the number of vertices in the graph. +// The pessimistic time complexity is O((|E| + |V|)⋅log|V|), where |E| is the +// number of edges and |V| the number of vertices in the graph. func ShortestPath(g Iterator, v, w int) (path []int, dist int64) { - parent, distances := ShortestPaths(g, v) - path, dist = []int{}, distances[w] - if dist == -1 { - return - } - for v := w; v != -1; v = parent[v] { - path = append(path, v) + n := g.Order() + dists := make([]int64, n) + parents := make([]int, n) + for i := range dists { + dists[i], parents[i] = -1, -1 } - for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { - path[i], path[j] = path[j], path[i] + dists[v] = 0 + + // Dijkstra's algorithm + q := emptyPrioQueue(dists) + q.Push(v) + for q.Len() > 0 { + u := q.Pop() + if u == w { + for x := w; x != -1; x = parents[x] { + path = append(path, x) + } + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + return path, dists[w] + } + g.Visit(u, func(x int, d int64) (skip bool) { + if d < 0 { + return + } + alt := dists[u] + d + switch { + case dists[x] == -1: + dists[x], parents[x] = alt, u + q.Push(x) + case alt < dists[x]: + dists[x], parents[x] = alt, u + q.Fix(x) + } + return + }) } - return + return []int{}, -1 } // ShortestPaths computes the shortest paths from v to all other vertices. diff --git a/path_test.go b/path_test.go index 2350c8c..ea29850 100644 --- a/path_test.go +++ b/path_test.go @@ -50,16 +50,76 @@ func TestShortestPath(t *testing.T) { } } +func randomGraph(n int) (*Mutable, int) { + g := New(n) + h := n / 2 + var t int + for i := 0; i < n; i++ { + g.Add(0, rand.Intn(n)) + if i == h { + t = rand.Intn(n) + g.Add(rand.Intn(n), t) + } else { + g.Add(rand.Intn(n), rand.Intn(n)) + } + } + return g, t +} + +// Store benchmark results as global variables to prevent unwanted optimizations. +var path []int +var dist int64 + +func BenchmarkShortestPath250(b *testing.B) { + g, t := randomGraph(250) + b.ResetTimer() + var p []int + var d int64 + for i := 0; i < b.N; i++ { + p, d = ShortestPath(g, 0, t) + } + path, dist = p, d +} + +func BenchmarkShortestPath500(b *testing.B) { + g, t := randomGraph(500) + b.ResetTimer() + var p []int + var d int64 + for i := 0; i < b.N; i++ { + p, d = ShortestPath(g, 0, t) + } + path, dist = p, d +} + +func BenchmarkShortestPath1000(b *testing.B) { + g, t := randomGraph(1000) + b.ResetTimer() + var p []int + var d int64 + for i := 0; i < b.N; i++ { + p, d = ShortestPath(g, 0, t) + } + path, dist = p, d +} + +var ( + parent []int + distances []int64 +) + func BenchmarkShortestPaths(b *testing.B) { n := 1000 - b.StopTimer() g := New(n) for i := 0; i < n; i++ { g.Add(0, rand.Intn(n)) g.Add(rand.Intn(n), rand.Intn(n)) } - b.StartTimer() + b.ResetTimer() + var p []int + var d []int64 for i := 0; i < b.N; i++ { - _, _ = ShortestPaths(g, 0) + p, d = ShortestPaths(g, 0) } + parent, distances = p, d } From 2c7e25331dc4a7627ab4c06943f89b15731247cf Mon Sep 17 00:00:00 2001 From: "Artur M. Wolff" Date: Thu, 27 Aug 2020 23:02:45 +0200 Subject: [PATCH 2/2] Fix ShortestPath comment --- path.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/path.go b/path.go index deafc11..b791718 100644 --- a/path.go +++ b/path.go @@ -4,8 +4,8 @@ package graph // Only edges with non-negative costs are included. // The number dist is the length of the path, or -1 if w cannot be reached. // -// The pessimistic time complexity is O((|E| + |V|)⋅log|V|), where |E| is the -// number of edges and |V| the number of vertices in the graph. +// The time complexity is O((|E| + |V|)⋅log|V|), where |E| is the number of edges +// and |V| the number of vertices in the graph. func ShortestPath(g Iterator, v, w int) (path []int, dist int64) { n := g.Order() dists := make([]int64, n)