Skip to content

Commit

Permalink
Merge pull request #5 from yourbasic/tip
Browse files Browse the repository at this point in the history
Tip
  • Loading branch information
korthaj authored Sep 21, 2017
2 parents 4e527a4 + eb25885 commit 40eb135
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 110 deletions.
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])
}

0 comments on commit 40eb135

Please sign in to comment.