golang数据结构
A graph is a representation of a network structure. There are tons of graph real world examples, the Internet and the social graph being the classic ones.
图是网络结构的表示。 有大量的图现实世界示例,互联网和社交图就是经典的例子。
It’s basically a set of nodes connected by edges.
它基本上是由edge连接的一组节点 。
I’ll skip the mathematical concepts since you can find them everywhere and jump directly to a Go implementation of a graph.
我将跳过数学概念,因为您可以在任何地方找到它们,然后直接跳转到图的Go实现。
实作 (Implementation)
A graph data structure will expose those methods:
图形数据结构将公开这些方法:
AddNode()
inserts a nodeAddNode()
插入一个节点AddEdge()
adds an edge between two nodesAddEdge()
在两个节点之间添加一条边
and String()
for inspection purposes.
和String()
用于检查目的。
A Graph is defined as
图定义为
type ItemGraph struct {
nodes []*Node
edges map[Node][]*Node
lock sync.RWMutex
}
with Node being
与节点是
type Node struct {
value Item
}
I’ll implement an undirected graph, which means that adding an edge from A to B also adds an edge from B to A.
我将实现一个无向图,这意味着从A到B添加一条边也会从B到A添加一条边。
实作 (Implementation)
// Package graph creates a ItemGraph data structure for the Item type
package graph
import (
"fmt"
"sync"
"github.com/cheekybits/genny/generic"
)
// Item the type of the binary search tree
type Item generic.Type
// Node a single node that composes the tree
type Node struct {
value Item
}
func (n *Node) String() string {
return fmt.Sprintf("%v", n.value)
}
// ItemGraph the Items graph
type ItemGraph struct {
nodes []*Node
edges map[Node][]*Node
lock sync.RWMutex
}
// AddNode adds a node to the graph
func (g *ItemGraph) AddNode(n *Node) {
g.lock.Lock()
g.nodes = append(g.nodes, n)
g.lock.Unlock()
}
// AddEdge adds an edge to the graph
func (g *ItemGraph) AddEdge(n1, n2 *Node) {
g.lock.Lock()
if g.edges == nil {
g.edges = make(map[Node][]*Node)
}
g.edges[*n1] = append(g.edges[*n1], n2)
g.edges[*n2] = append(g.edges[*n2], n1)
g.lock.Unlock()
}
// AddEdge adds an edge to the graph
func (g *ItemGraph) String() {
g.lock.RLock()
s := ""
for i := 0; i < len(g.nodes); i++ {
s += g.nodes[i].String() + " -> "
near := g.edges[*g.nodes[i]]
for j := 0; j < len(near); j++ {
s += near[j].String() + " "
}
s += "\n"
}
fmt.Println(s)
g.lock.RUnlock()
}
Quite straightforward. Here’s a test that when run, will populate the graph and print
非常简单。 这是一个测试,运行时将填充图形并打印
$ go test
A -> B C D
B -> A E
C -> A E
D -> A
E -> B C F
F -> E
PASS
package graph
import (
"fmt"
"testing"
)
var g ItemGraph
func fillGraph() {
nA := Node{"A"}
nB := Node{"B"}
nC := Node{"C"}
nD := Node{"D"}
nE := Node{"E"}
nF := Node{"F"}
g.AddNode(&nA)
g.AddNode(&nB)
g.AddNode(&nC)
g.AddNode(&nD)
g.AddNode(&nE)
g.AddNode(&nF)
g.AddEdge(&nA, &nB)
g.AddEdge(&nA, &nC)
g.AddEdge(&nB, &nE)
g.AddEdge(&nC, &nE)
g.AddEdge(&nE, &nF)
g.AddEdge(&nD, &nA)
}
func TestAdd(t *testing.T) {
fillGraph()
g.String()
}
遍历图形:BFS (Traversing the graph: BFS)
BFS (Breadth-First Search) is one of the most widely known algorithm to traverse a graph. Starting from a node, it first traverses all its directly linked nodes, then processes the nodes linked to those, and so on.
BFS (宽度优先搜索)是遍历图形的最广为人知的算法之一。 从节点开始,它首先遍历其所有直接链接的节点,然后处理链接到这些节点的节点,依此类推。
It’s implemented using a queue, which is generated from my Go implementation of a queue data structure with code generation for the node type:
它是使用队列实现的,该队列是从我对队列数据结构的Go实现中生成的,并为节点类型生成了代码:
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package graph
import "sync"
// NodeQueue the queue of Nodes
type NodeQueue struct {
items []Node
lock sync.RWMutex
}
// New creates a new NodeQueue
func (s *NodeQueue) New() *NodeQueue {
s.lock.Lock()
s.items = []Node{}
s.lock.Unlock()
return s
}
// Enqueue adds an Node to the end of the queue
func (s *NodeQueue) Enqueue(t Node) {
s.lock.Lock()
s.items = append(s.items, t)
s.lock.Unlock()
}
// Dequeue removes an Node from the start of the queue
func (s *NodeQueue) Dequeue() *Node {
s.lock.Lock()
item := s.items[0]
s.items = s.items[1:len(s.items)]
s.lock.Unlock()
return &item
}
// Front returns the item next in the queue, without removing it
func (s *NodeQueue) Front() *Node {
s.lock.RLock()
item := s.items[0]
s.lock.RUnlock()
return &item
}
// IsEmpty returns true if the queue is empty
func (s *NodeQueue) IsEmpty() bool {
s.lock.RLock()
defer s.lock.RUnlock()
return len(s.items) == 0
}
// Size returns the number of Nodes in the queue
func (s *NodeQueue) Size() int {
s.lock.RLock()
defer s.lock.RUnlock()
return len(s.items)
}
导线法 (Traverse method)
Here’s the BFS implementation:
这是BFS的实现:
// Traverse implements the BFS traversing algorithm
func (g *ItemGraph) Traverse(f func(*Node)) {
g.lock.RLock()
q := NodeQueue{}
q.New()
n := g.nodes[0]
q.Enqueue(*n)
visited := make(map[*Node]bool)
for {
if q.IsEmpty() {
break
}
node := q.Dequeue()
visited[node] = true
near := g.edges[*node]
for i := 0; i < len(near); i++ {
j := near[i]
if !visited[j] {
q.Enqueue(*j)
visited[j] = true
}
}
if f != nil {
f(node)
}
}
g.lock.RUnlock()
}
which can be tested with
可以用
func TestTraverse(t *testing.T) {
g.Traverse(func(n *Node) {
fmt.Printf("%v\n", n)
})
}
which after being added to our tests, will print the road it took to traverse all our nodes:
在添加到我们的测试中之后,它将打印遍历我们所有节点所花费的道路:
A
B
C
D
A
E
F
创建一个具体的图形数据结构 (Creating a concrete graph data structure)
You can use this generic implemenation to generate type-specific graphs, using
您可以使用以下通用实现来生成特定于类型的图,方法是:
//generate a `IntGraph` graph of `int` values
genny -in graph.go -out graph-int.go gen "Item=int"
//generate a `StringGraph` graph of `string` values
genny -in graph.go -out graph-string.go gen "Item=string"
golang数据结构