Skip to content
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
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Your basic graph
# Your basic graph [![GoDoc](https://godoc.org/github.com/yourbasic/graph?status.svg)][godoc-graph]

### Golang library of basic graph algorithms

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion build/cartesian.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 2 additions & 0 deletions build/cycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion build/tensor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
109 changes: 109 additions & 0 deletions heap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package graph

type prioQueue struct {
heap []int // vertices in heap order
index []int // index of each vertex in the heap
cost []int64
}

func emptyPrioQueue(cost []int64) *prioQueue {
return &prioQueue{
index: make([]int, len(cost)),
cost: cost,
}
}

func newPrioQueue(cost []int64) *prioQueue {
n := len(cost)
q := &prioQueue{
heap: make([]int, n),
index: make([]int, n),
cost: cost,
}
for i := range q.heap {
q.heap[i] = i
q.index[i] = i
}
return q
}

// Len returns the number of elements in the queue.
func (q *prioQueue) Len() int {
return len(q.heap)
}

// 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)
}

// 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
}

// Contains tells whether v is in the queue.
func (q *prioQueue) Contains(v int) bool {
return q.index[v] >= 0
}

// 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 (q *prioQueue) less(i, j int) bool {
return q.cost[q.heap[i]] < q.cost[q.heap[j]]
}

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
}
60 changes: 3 additions & 57 deletions mst.go
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -22,67 +18,17 @@ func MST(g Iterator) (parent []int) {
}

// Prim's algorithm
Q := newMstQueue(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
})
}
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
}
54 changes: 5 additions & 49 deletions path.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -44,10 +40,10 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) {
dist[v] = 0

// Dijkstra's algorithm
Q := emptySpQueue(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
Expand All @@ -56,53 +52,13 @@ 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
})
}
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])
}