From c5be31dff66615bf13dfc8e85599fb5de2ec6272 Mon Sep 17 00:00:00 2001 From: korthaj Date: Mon, 19 Jun 2017 10:08:47 +0200 Subject: [PATCH 1/8] Add godoc badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5e06d17..d51df7e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ### Golang library of basic graph algorithms +[![GoDoc](https://godoc.org/github.com/yourbasic/graph?status.svg)][godoc-graph] + ![Topological ordering](top.png) *Topological ordering, image by [David Eppstein][de], [CC0 1.0][cc010].* From 657bb06430f39ceabb9e4bd6d04d55c64eb39bf9 Mon Sep 17 00:00:00 2001 From: korthaj Date: Mon, 19 Jun 2017 18:58:37 +0200 Subject: [PATCH 2/8] Move godoc badge --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index d51df7e..9cc0a27 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# Your basic graph +# Your basic graph [![GoDoc](https://godoc.org/github.com/yourbasic/graph?status.svg)][godoc-graph] ### Golang library of basic graph algorithms -[![GoDoc](https://godoc.org/github.com/yourbasic/graph?status.svg)][godoc-graph] - ![Topological ordering](top.png) *Topological ordering, image by [David Eppstein][de], [CC0 1.0][cc010].* From 06a1ea3aa6c3eef7320221a07bf4de6ab9a7a8bf Mon Sep 17 00:00:00 2001 From: korthaj Date: Fri, 23 Jun 2017 19:22:18 +0200 Subject: [PATCH 3/8] Fix edge notation in doc --- build/cartesian.go | 2 +- build/tensor.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/cartesian.go b/build/cartesian.go index 056065a..ae7f962 100644 --- a/build/cartesian.go +++ b/build/cartesian.go @@ -6,7 +6,7 @@ import "strconv" // a graph whose vertices correspond to ordered pairs (v1, v2), // where v1 and v2 are vertices in g1 and g2, respectively. // The vertices (v1, v2) and (w1, w2) are connected by an edge if -// v1 = w1 and (v2, w2) ∊ g2 or v2 = w2 and (v1, w1) ∊ g1. +// v1 = w1 and {v2, w2} ∊ g2 or v2 = w2 and {v1, w1} ∊ g1. // // In the new graph, vertex (v1, v2) gets index n⋅v1 + v2, where n = g2.Order(), // and index i corresponds to the vertice (i/n, i%n). diff --git a/build/tensor.go b/build/tensor.go index 6168c21..1bfd46f 100644 --- a/build/tensor.go +++ b/build/tensor.go @@ -6,7 +6,7 @@ import "strconv" // a graph whose vertices correspond to ordered pairs (v1, v2), // where v1 and v2 are vertices in g1 and g2, respectively. // The vertices (v1, v2) and (w1, w2) are connected by an edge whenever -// both of the edges (v1, w1) and (v2, w2) exist in the original graphs. +// both of the edges {v1, w1} and {v2, w2} exist in the original graphs. // // In the new graph, vertex (v1, v2) gets index n⋅v1 + v2, where n = g2.Order(), // and index i corresponds to the vertice (i/n, i%n). From 8a799a9c1640d6d6f4552a2c0d42c799f0aa3386 Mon Sep 17 00:00:00 2001 From: korthaj Date: Sat, 24 Jun 2017 10:40:31 +0200 Subject: [PATCH 4/8] Document preconditions in cycle --- build/cycle.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/cycle.go b/build/cycle.go index 3c171ae..d3b3ccc 100644 --- a/build/cycle.go +++ b/build/cycle.go @@ -22,8 +22,10 @@ func Cycle(n int) *Virtual { return }) + // Precondition : n ≥ 3. g.degree = func(v int) int { return 2 } + // Precondition : n ≥ 3. g.visit = func(v int, a int, do func(w int, c int64) bool) (aborted bool) { var w [2]int switch v { From e69819a0a1945b78caab62e68c194c9e10ea07b0 Mon Sep 17 00:00:00 2001 From: korthaj Date: Sat, 24 Jun 2017 17:30:52 +0200 Subject: [PATCH 5/8] Update Virtual section in README --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cc0a27..819394f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ directed edges (v, w) and (w, v), both of cost c. A self-loop, an edge connecting a vertex to itself, is both directed and undirected. + ### Graph data structures The type `Mutable` represents a directed graph with a fixed number @@ -42,26 +43,56 @@ with its adjacent vertices. This makes for fast and predictable iteration: the Visit method produces its elements by reading from a fixed sorted precomputed list. + ### Virtual graphs -The subpackage `graph/build` offers a tool for building virtual graphs. +The subpackage `graph/build` offers a tool for building graphs of type `Virtual`. + In a virtual graph no vertices or edges are stored in memory, they are instead computed as needed. New virtual graphs are constructed by composing and filtering a set of standard graphs, or by writing functions that describe the edges of a graph. +The following standard graphs are predefined: + +- empty graphs, +- complete graphs and complete bipartite graphs, +- grid graphs and complete *k*-ary trees, +- cycle graphs and circulant graphs, +- and hypergraphs. + +The following operations are supported: + +- adding and deleting sets of edges, +- adding cost functions, +- filtering graphs by edge functions, +- complement, intersection and union, +- subgraphs, +- connecting graphs at a single vertex, +- joining two graphs by a set of edges, +- matching two graphs by a set of edges, +- cartesian product and tensor product. + +Non-virtual graphs can be imported, and used as building blocks, +by the `Specific` function. Virtual graphs don't need to be “exported‬”; +they implement the `Iterator` interface and hence can be used directly +by any algorithm in the graph package. + + ### Installation Once you have [installed Go][golang-install], run this command to install the `graph` package: go get github.com/yourbasic/graph + ### Documentation There is an online reference for the package at [godoc.org/github.com/yourbasic/graph][godoc-graph]. + ### Roadmap * The API of this library is frozen. From 41f3569e563b11e1a18f42130f5775b7ecdb14b1 Mon Sep 17 00:00:00 2001 From: korthaj Date: Thu, 21 Sep 2017 09:27:52 +0200 Subject: [PATCH 6/8] Refactor prio queue --- mst.go | 52 +--------------------------------------------------- path.go | 42 +----------------------------------------- 2 files changed, 2 insertions(+), 92 deletions(-) diff --git a/mst.go b/mst.go index 0fe9d84..89dd801 100644 --- a/mst.go +++ b/mst.go @@ -22,7 +22,7 @@ func MST(g Iterator) (parent []int) { } // Prim's algorithm - Q := newMstQueue(cost) + Q := newQueue(cost) for Q.Len() > 0 { v := heap.Pop(Q).(int) g.Visit(v, func(w int, c int64) (skip bool) { @@ -36,53 +36,3 @@ func MST(g Iterator) (parent []int) { } return } - -type mstQueue struct { - heap []int // vertices in heap order - index []int // index of each vertex in the heap - cost []int64 -} - -func newMstQueue(cost []int64) *mstQueue { - n := len(cost) - h := &mstQueue{ - heap: make([]int, n), - index: make([]int, n), - cost: cost, - } - for i := range h.heap { - h.heap[i] = i - h.index[i] = i - } - return h -} - -func (m *mstQueue) Len() int { return len(m.heap) } - -func (m *mstQueue) Less(i, j int) bool { - return m.cost[m.heap[i]] < m.cost[m.heap[j]] -} - -func (m *mstQueue) Swap(i, j int) { - m.heap[i], m.heap[j] = m.heap[j], m.heap[i] - m.index[m.heap[i]] = i - m.index[m.heap[j]] = j -} - -func (m *mstQueue) Push(x interface{}) {} // Not used - -func (m *mstQueue) Pop() interface{} { - n := len(m.heap) - 1 - v := m.heap[n] - m.index[v] = -1 - m.heap = m.heap[:n] - return v -} - -func (m *mstQueue) Update(v int) { - heap.Fix(m, m.index[v]) -} - -func (m *mstQueue) Contains(v int) bool { - return m.index[v] >= 0 -} diff --git a/path.go b/path.go index 1ab9134..4bba15f 100644 --- a/path.go +++ b/path.go @@ -44,7 +44,7 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { dist[v] = 0 // Dijkstra's algorithm - Q := emptySpQueue(dist) + Q := emptyQueue(dist) heap.Push(Q, v) for Q.Len() > 0 { v = heap.Pop(Q).(int) @@ -66,43 +66,3 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { } return } - -type spQueue struct { - heap []int // vertices in heap order - index []int // index of each vertex in the heap - dist []int64 -} - -func emptySpQueue(dist []int64) *spQueue { - return &spQueue{dist: dist, index: make([]int, len(dist))} -} - -func (pq *spQueue) Len() int { return len(pq.heap) } - -func (pq *spQueue) Less(i, j int) bool { - return pq.dist[pq.heap[i]] < pq.dist[pq.heap[j]] -} - -func (pq *spQueue) Swap(i, j int) { - pq.heap[i], pq.heap[j] = pq.heap[j], pq.heap[i] - pq.index[pq.heap[i]] = i - pq.index[pq.heap[j]] = j -} - -func (pq *spQueue) Push(x interface{}) { - n := len(pq.heap) - v := x.(int) - pq.heap = append(pq.heap, v) - pq.index[v] = n -} - -func (pq *spQueue) Pop() interface{} { - n := len(pq.heap) - 1 - v := pq.heap[n] - pq.heap = pq.heap[:n] - return v -} - -func (pq *spQueue) Update(v int) { - heap.Fix(pq, pq.index[v]) -} From d79be356761a189259b58becdca58ed3b5b76d98 Mon Sep 17 00:00:00 2001 From: korthaj Date: Thu, 21 Sep 2017 09:30:27 +0200 Subject: [PATCH 7/8] Refactor prio queue --- heap.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 heap.go diff --git a/heap.go b/heap.go new file mode 100644 index 0000000..a0f0056 --- /dev/null +++ b/heap.go @@ -0,0 +1,67 @@ +package graph + +import ( + "container/heap" +) + +type prioQueue struct { + heap []int // vertices in heap order + index []int // index of each vertex in the heap + cost []int64 +} + +func emptyQueue(cost []int64) *prioQueue { + return &prioQueue{ + index: make([]int, len(cost)), + cost: cost, + } +} + +func newQueue(cost []int64) *prioQueue { + n := len(cost) + h := &prioQueue{ + heap: make([]int, n), + index: make([]int, n), + cost: cost, + } + for i := range h.heap { + h.heap[i] = i + h.index[i] = i + } + return h +} + +func (m *prioQueue) Len() int { return len(m.heap) } + +func (m *prioQueue) Less(i, j int) bool { + return m.cost[m.heap[i]] < m.cost[m.heap[j]] +} + +func (m *prioQueue) Swap(i, j int) { + m.heap[i], m.heap[j] = m.heap[j], m.heap[i] + m.index[m.heap[i]] = i + m.index[m.heap[j]] = j +} + +func (pq *prioQueue) Push(x interface{}) { + n := len(pq.heap) + v := x.(int) + pq.heap = append(pq.heap, v) + pq.index[v] = n +} + +func (m *prioQueue) Pop() interface{} { + n := len(m.heap) - 1 + v := m.heap[n] + m.index[v] = -1 + m.heap = m.heap[:n] + return v +} + +func (m *prioQueue) Update(v int) { + heap.Fix(m, m.index[v]) +} + +func (m *prioQueue) Contains(v int) bool { + return m.index[v] >= 0 +} From eb258854c346e297e2a0534b662e8ecfe788977f Mon Sep 17 00:00:00 2001 From: korthaj Date: Thu, 21 Sep 2017 16:21:52 +0200 Subject: [PATCH 8/8] Replace container/heap with native implementation. --- heap.go | 108 +++++++++++++++++++++++++++++++++++++++----------------- mst.go | 10 ++---- path.go | 14 +++----- 3 files changed, 83 insertions(+), 49 deletions(-) diff --git a/heap.go b/heap.go index a0f0056..2fee3f0 100644 --- a/heap.go +++ b/heap.go @@ -1,67 +1,109 @@ package graph -import ( - "container/heap" -) - type prioQueue struct { heap []int // vertices in heap order index []int // index of each vertex in the heap cost []int64 } -func emptyQueue(cost []int64) *prioQueue { +func emptyPrioQueue(cost []int64) *prioQueue { return &prioQueue{ index: make([]int, len(cost)), cost: cost, } } -func newQueue(cost []int64) *prioQueue { +func newPrioQueue(cost []int64) *prioQueue { n := len(cost) - h := &prioQueue{ + q := &prioQueue{ heap: make([]int, n), index: make([]int, n), cost: cost, } - for i := range h.heap { - h.heap[i] = i - h.index[i] = i + for i := range q.heap { + q.heap[i] = i + q.index[i] = i } - return h + return q } -func (m *prioQueue) Len() int { return len(m.heap) } +// Len returns the number of elements in the queue. +func (q *prioQueue) Len() int { + return len(q.heap) +} -func (m *prioQueue) Less(i, j int) bool { - return m.cost[m.heap[i]] < m.cost[m.heap[j]] +// Push pushes v onto the queue. +// The time complexity is O(log n) where n = q.Len(). +func (q *prioQueue) Push(v int) { + n := q.Len() + q.heap = append(q.heap, v) + q.index[v] = n + q.up(n) } -func (m *prioQueue) Swap(i, j int) { - m.heap[i], m.heap[j] = m.heap[j], m.heap[i] - m.index[m.heap[i]] = i - m.index[m.heap[j]] = j +// Pop removes the minimum element from the queue and returns it. +// The time complexity is O(log n) where n = q.Len(). +func (q *prioQueue) Pop() int { + n := q.Len() - 1 + q.swap(0, n) + q.down(0, n) + + v := q.heap[n] + q.index[v] = -1 + q.heap = q.heap[:n] + return v } -func (pq *prioQueue) Push(x interface{}) { - n := len(pq.heap) - v := x.(int) - pq.heap = append(pq.heap, v) - pq.index[v] = n +// Contains tells whether v is in the queue. +func (q *prioQueue) Contains(v int) bool { + return q.index[v] >= 0 } -func (m *prioQueue) Pop() interface{} { - n := len(m.heap) - 1 - v := m.heap[n] - m.index[v] = -1 - m.heap = m.heap[:n] - return v +// Fix re-establishes the ordering after the cost for v has changed. +// The time complexity is O(log n) where n = q.Len(). +func (q *prioQueue) Fix(v int) { + if i := q.index[v]; !q.down(i, q.Len()) { + q.up(i) + } } -func (m *prioQueue) Update(v int) { - heap.Fix(m, m.index[v]) +func (q *prioQueue) less(i, j int) bool { + return q.cost[q.heap[i]] < q.cost[q.heap[j]] } -func (m *prioQueue) Contains(v int) bool { - return m.index[v] >= 0 +func (q *prioQueue) swap(i, j int) { + q.heap[i], q.heap[j] = q.heap[j], q.heap[i] + q.index[q.heap[i]] = i + q.index[q.heap[j]] = j +} + +func (q *prioQueue) up(j int) { + for { + i := (j - 1) / 2 // parent + if i == j || !q.less(j, i) { + break + } + q.swap(i, j) + j = i + } +} + +func (q *prioQueue) down(i0, n int) bool { + i := i0 + for { + j1 := 2*i + 1 + if j1 >= n || j1 < 0 { // j1 < 0 after int overflow + break + } + j := j1 // left child + if j2 := j1 + 1; j2 < n && q.less(j2, j1) { + j = j2 // = 2*i + 2 // right child + } + if !q.less(j, i) { + break + } + q.swap(i, j) + i = j + } + return i > i0 } diff --git a/mst.go b/mst.go index 89dd801..dc72292 100644 --- a/mst.go +++ b/mst.go @@ -1,9 +1,5 @@ package graph -import ( - "container/heap" -) - // MST computes a minimum spanning tree for each connected component // of an undirected weighted graph. // The forest of spanning trees is returned as a slice of parent pointers: @@ -22,13 +18,13 @@ func MST(g Iterator) (parent []int) { } // Prim's algorithm - Q := newQueue(cost) + Q := newPrioQueue(cost) for Q.Len() > 0 { - v := heap.Pop(Q).(int) + v := Q.Pop() g.Visit(v, func(w int, c int64) (skip bool) { if Q.Contains(w) && c < cost[w] { cost[w] = c - Q.Update(w) + Q.Fix(w) parent[w] = v } return diff --git a/path.go b/path.go index 4bba15f..850ddec 100644 --- a/path.go +++ b/path.go @@ -1,9 +1,5 @@ package graph -import ( - "container/heap" -) - // ShortestPath computes a shortest path from v to w. // Only edges with non-negative costs are included. // The number dist is the length of the path, or -1 if w cannot be reached. @@ -44,10 +40,10 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { dist[v] = 0 // Dijkstra's algorithm - Q := emptyQueue(dist) - heap.Push(Q, v) + Q := emptyPrioQueue(dist) + Q.Push(v) for Q.Len() > 0 { - v = heap.Pop(Q).(int) + v := Q.Pop() g.Visit(v, func(w int, d int64) (skip bool) { if d < 0 { return @@ -56,10 +52,10 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { switch { case dist[w] == -1: dist[w], parent[w] = alt, v - heap.Push(Q, w) + Q.Push(w) case alt < dist[w]: dist[w], parent[w] = alt, v - Q.Update(w) + Q.Fix(w) } return })