Go的数据结构与实现【Graph】

介绍

图是网络结构的表示。现实世界中有大量图谱示例,互联网和社交图谱就是经典示例。图基本上是一组由边连接的节点。

实现

实现思路

图形数据结构将实现这些方法:

AddNode():添加一个节点到图里

AddEdge():添加一条边到图里

Print():打印图结构

图结构定义为:

go 复制代码
type Graph struct {
   sync.RWMutex
   nodes []*Node
   edges map[Node][]*Node
}

节点定义为:

go 复制代码
import "fmt"

type T string

type Node struct {
   value T
}

// Print a node
func (n *Node) Print() string {
   return fmt.Sprintf("%v", n.value)
}

这里将实现一个无向图,这意味着从A到B添加一条边也会从B到A添加一条边。

go 复制代码
func NewGraph() *Graph {
   g := &Graph{
      nodes: []*Node{},
      edges: make(map[Node][]*Node),
   }

   return g
}

// AddNode adds a node to the graph
func (g *Graph) AddNode(n *Node) {
   g.Lock()
   g.nodes = append(g.nodes, n)
   g.Unlock()
}

// AddEdge adds an edge to the graph
func (g *Graph) AddEdge(n1, n2 *Node) {
   g.Lock()
   defer g.Unlock()

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

// Print whole graph
func (g *Graph) Print() {
   g.Lock()
   defer g.Unlock()

   ret := ""
   for i := 0; i < len(g.nodes); i++ {
      ret += g.nodes[i].Print() + " -> "
      neighborhood := g.edges[*g.nodes[i]]
      for j := 0; j < len(neighborhood); j++ {
         ret += neighborhood[j].Print() + " "
      }
      ret += "\n"
   }

   fmt.Println(ret)
}

单元测试

这是一个测试,运行时将填充图结构并打印:

go 复制代码
import "testing"

var (
   nA = &Node{"A"}
   nB = &Node{"B"}
   nC = &Node{"C"}
   nD = &Node{"D"}
   nE = &Node{"E"}
   nF = &Node{"F"}
)

func InitGraph() *Graph {
   g := NewGraph()
   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)

   return g
}

func TestGraph_Print(t *testing.T) {
   g := InitGraph()
   g.Print()
}

输出:

go 复制代码
A -> B C D 
B -> A E 
C -> A E 
D -> A 
E -> B C F 
F -> E 

--- PASS: TestGraph_Print (0.00s)
PASS

遍历

BFS(广度优先搜索)是最广为人知的遍历图的算法之一。从一个节点开始,它首先遍历其所有直接链接的节点,然后处理链接到这些节点的节点,依此类推。

它是使用队列实现的,我们可以用之前实现的队列数据结构来辅助完成这个算法:

go 复制代码
// Traverse implements the BFS traversing algorithm
func (g *Graph) Traverse(f func(*Node)) {
   g.RLock()
   q := NewNodeQueue()
   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.RUnlock()
}

我们对算法进行测试:

go 复制代码
func TestGraph_Traverse(t *testing.T) {
   g := InitGraph()
   g.Traverse(func(n *Node) {
      fmt.Printf("%v\n", n.value)
   })
}

输出结果:

go 复制代码
A
B
C
D
E
F
--- PASS: TestGraph_Traverse (0.00s)
PASS
相关推荐
FuLLovers16 分钟前
2024-09-13 冯诺依曼体系结构 OS管理 进程
linux·开发语言
王中阳Go20 分钟前
字节跳动的微服务独家面经
微服务·面试·golang
everyStudy1 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
luthane1 小时前
python 实现average mean平均数算法
开发语言·python·算法
凡人的AI工具箱2 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
sleP4o2 小时前
Python操作MySQL
开发语言·python·mysql
是店小二呀2 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
洛寒瑜2 小时前
【读书笔记-《30天自制操作系统》-23】Day24
开发语言·汇编·笔记·操作系统·应用程序
ephemerals__2 小时前
【c++】动态内存管理
开发语言·c++
manba_2 小时前
leetcode-560. 和为 K 的子数组
数据结构·算法·leetcode