Golang程序实现Dijkstra算法来查找图中两个节点之间的最短路径
在计算机科学中,Dijkstra算法是一种图算法,主要用于计算图中节点之间的最短路径。这种算法是以荷兰计算机科学家Edsger W. Dijkstra命名的,他在1956年的论文中首次提出了这个算法,用于解决单源最短路径问题。Dijkstra算法时间复杂度为O(n^2),但是对于优化后的算法可以达到O(E+VlogV)。在本文中,我们将会通过Golang代码实现Dijkstra算法,用于查找图中任意两个节点之间的最短路径。
算法介绍
Dijkstra算法是一种贪心算法,以一定的顺序搜索图中的节点。这个算法的核心思想是:找到当前距离起点最近的节点,并将此节点添加到已处理列表中。通过处理节点的相邻节点,更新最短路径信息。
假设给定的图G含有n个节点和m条边。我们设起点为节点s,终点为节点t。对于每个节点i,我们定义d[i]为从起点s到节点i的最短路径的长度。我们定义一个集合S,其中包含已处理的节点,这些节点已经找到了从初始节点到S中的节点的最短路径。
在每次迭代中,我们从S中找到一个离起点最近的节点u,尚未处理过此节点。我们将u添加到S中,对于u的每一个邻居节点v,在更新节点v的距离和父节点之前,我们检查是否经过u可以更快地到达v,如果是,则我们更新d[v]和v的父节点为u。这个算法一直迭代到所有节点都被添加到S中为止。
Golang代码实现
我们将在Golang中实现Dijkstra算法,用于查找图中任意两个节点之间的最短路径。我们先定义一个结构体表示图中的节点:
type node struct {
id int // 节点id
dist int // 节点到起点的距离
prev *node // 节点的父节点
next []*node // 节点邻居数组
}
上述结构体中的,id代表节点的编号;dist代表节点到起点的距离;prev代表父节点;next数组代表节点的邻居。我们可以使用一个图数据结构来存储所有节点:
type Graph struct {
nodes []*node // 节点数组
edges map[int]map[int]int // 边的权重
}
上述结构体中,nodes数组存储所有节点;edges是一个包含键值对的字典,键是从节点i到节点j的边,值是这条边权重的大小。我们可以定义Dijkstra算法函数,用于查找图中任意两个节点之间的最短路径:
func Dijkstra(start, end int, g *Graph) []*node {
s := make(map[int]*node) // 定义已访问过的节点
q := make([]*node, len(g.nodes)) // 定义节点队列
for i, n := range g.nodes {
q[i] = &node{id: n.id, dist: math.MaxInt32} // 初始化节点距离为无穷大
if n.id == start {
q[i].dist = 0 // 起点距离为0
}
}
heap.Init(nodeHeap(q)) // 以节点距离建立小顶堆
for len(q) > 0 {
u := heap.Pop(nodeHeap(q)).(*node)
if u.id==end {
return backTrace(end, s)
}
s[u.id] = u // 标记节点为已访问
for _, v := range u.next {
if _, ok := s[v.id]; ok {
continue // 已访问过的节点不处理
}
alt := u.dist + g.edges[u.id][v.id]
if alt < q[nodeIndex(v.id)].dist { // 更新最短路径信息
q[nodeIndex(v.id)].dist = alt
q[nodeIndex(v.id)].prev = u
heap.Fix(nodeHeap(q), nodeIndex(v.id))
}
}
}
return []*node{} // 没有找到路径
}
在上述代码中,我们首先初始化一个节点队列和一个已访问过节点的字典。然后我们以节点距离为关键字建立小顶堆,每次从堆中取出一个距离最短的节点进行处理。用于标记节点的访问状态,并更新相邻节点的距离和父节点。如果我们找到终点,我们将回溯路径并将其返回。
下面是一个完整的示例程序:
package main
import (
"container/heap"
"fmt"
"math"
)
type node struct {
id int
dist int
prev *node
next []*node
}
type Graph struct {
nodes []*node
edges map[int]map[int]int
}
func Dijkstra(start, end int, g *Graph) []*node {
s := make(map[int]*node)
q := make([]*node, len(g.nodes))
for i, n := range g.nodes {
q[i] = &node{id: n.id, dist: math.MaxInt32}
if n.id == start {
q[i].dist = 0
}
}
heap.Init(nodeHeap(q))
for len(q) > 0 {
u := heap.Pop(nodeHeap(q)).(*node)
if u.id == end {
return backTrace(end, s)
}
s[u.id] = u
for _, v := range u.next {
if _, ok := s[v.id]; ok {
continue
}
alt := u.dist + g.edges[u.id][v.id]
if alt < q[nodeIndex(v.id)].dist {
q[nodeIndex(v.id)].dist = alt
q[nodeIndex(v.id)].prev = u
heap.Fix(nodeHeap(q), nodeIndex(v.id))
}
}
}
return []*node{}
}
type nodeHeap []*node
func (h nodeHeap) Len() int {
return len(h)
}
func (h nodeHeap) Less(i, j int) bool {
return h[i].dist < h[j].dist
}
func (h nodeHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *nodeHeap) Push(x interface{}) {
*h = append(*h, x.(*node))
}
func (h *nodeHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func nodeIndex(id int) int {
for i, n := range q {
if n.id == id {
return i
}
}
return -1
}
func backTrace(id int, s map[int]*node) []*node {
path := []*node{}
for {
n, ok := s[id]
if !ok {
break
}
path = append([]*node{n}, path...)
id = n.prev.id
}
return path
}
func main() {
nA := &node{id: 1}
nB := &node{id: 2}
nC := &node{id: 3}
nD := &node{id: 4}
nE := &node{id: 5}
nF := &node{id: 6}
nG := &node{id: 7}
nH := &node{id: 8}
nA.next = []*node{nB, nC}
nB.next = []*node{nA, nD, nE}
nC.next = []*node{nA, nF}
nD.next = []*node{nB, nG}
nE.next = []*node{nB}
nF.next = []*node{nC, nH}
nG.next = []*node{nD, nH}
nH.next = []*node{nF, nG}
g := &Graph{
nodes: []*node{nA, nB, nC, nD, nE, nF, nG, nH},
edges: make(map[int]map[int]int),
}
g.edges[nA.id] = map[int]int{nB.id: 1, nC.id: 3}
g.edges[nB.id] = map[int]int{nA.id: 1, nD.id: 2, nE.id: 7}
g.edges[nC.id] = map[int]int{nA.id: 3, nF.id: 2}
g.edges[nD.id] = map[int]int{nB.id: 2, nG.id: 1}
g.edges[nE.id] = map[int]int{nB.id: 7}
g.edges[nF.id] = map[int]int{nC.id: 2, nH.id: 3}
g.edges[nG.id] = map[int]int{nD.id: 1, nH.id: 2}
g.edges[nH.id] = map[int]int{nF.id: 3, nG.id: 2}
path := Dijkstra(nA.id, nH.id, g)
if len(path) > 0 {
fmt.Printf("shortest path from %d to %d: ", nA.id, nH.id)
for _, n := range path {
fmt.Printf("%d ", n.id)
}
fmt.Println()
} else {
fmt.Println("no path found")
}
}
在上述示例程序中,我们定义了一个图,该图包含8个节点和14条边。我们使用刚才定义的Dijkstra函数来查找图中节点1到节点8之间的最短路径。
结论
在本文中,我们介绍了Dijkstra算法,这是一种用于查找图中任意两个节点最短路径的算法。我们使用Golang编写了一个Dijkstra函数,该函数可以通过传入起点和终点的节点编号和一个图数据结构,来使用Dijkstra算法查找任意两个节点之间的最短路径。这个算法可以在计算复杂度上得到很好的优化,实现高效的最短路径查找。
极客笔记