问题
假设地图上4个点A、B、C、D,有连线表示可通行,并且是单向,连线上标有距离,如何求出A到C的最短路径
输入:{A: {B:1,D:3},B:{D:1,C:2},D:{C:1}}
输出:3
思路
由起点开始,一层一层往外遍历,求出起点到每个点的最短路径,那么遍历到终点,从而也就得出了起点到终点的最短路径
实现
// 定义有向图,通过两级map存储有向图
type DirectedGraph map[int]map[int]int
// 迪克斯特拉
// graph:有向图
// start:起点
// end: 终点
// return:-1 没有路径 >=0 最短路径长度
func Dikstra(graph DirectedGraph,start int,end int) int {
// 通过map存储起点到指定点的最短路径 例如 {2:3} 表示起点到2这个点的最短距离是3
dstcMap := map[int]int{}
// 使用切片表示队列,用来有序遍历节点,首先把起点添加到队列中
q := []int{start}
// 遍历节点
for len(q) > 0 {
// 获取队列首部节点
node := q[0]
q = q[1:]
// 获取所有由节点出发的边集合
edges := graph[node]
// 遍历边集合
for n, w := range edges{
// 把边的终端节点添加到队列中
q = append(q, n)
// 获取通过该边的路径长度
dstcNew := w
if _,ok := dstcMap[node];ok {
dstcNew += dstcMap[node]
}
// 如果求出的路径长度比通过其他边路径长度短,则覆盖
if dstc,ok := dstcMap[n];!ok || dstc > dstcNew {
dstcMap[n] = dstcNew
}
}
}
// 返回最短路径
if dstc,ok := dstcMap[end];ok {
return dstc
}
return -1
}
测试
func main() {
directedGraph := DirectedGraph{1: {2:1,3:3},2:{3:1,4:2},3:{4:1}}
fmt.Println(Dikstra(directedGraph,1,4))
}
// map[2:1 3:2 4:3]
//3
通过对比结果与手动推算结果,代码能够输出正确的结果
绕圈问题
如果存在如图的绕圈问题,D可以到E,E也可以到D,如果循环遍历,那么程序就会陷入循环中,永远都不能输出结果
解决方案:添加一个集合,集合中记录通过哪条边的哪个节点已经处理过,遇到绕圈情况就可以跳过
代码实现:
// 通过结构map[int]map[int]int表示集合,好处是读取时间复杂度为O(1),缺点是会浪费一些空间
set := map[int]map[int]int{}
// 判断是否通过edgeStartNode到edgeEndNode的边处理过edgeEndNode(有点拗口)
if _, ok := set[edgeStartNode][edgeEndNode]; ok {
continue
}
// 添加队列成功,记录到set中,下次就不会重复添加到队列中
if _, ok := set[edgeStartNode]; !ok {
set[edgeStartNode] = map[int]int{}
}
set[edgeStartNode][edgeEndNode] = 1
整合后的代码:
// 定义有向图,通过两级map存储有向图
type DirectedGraph map[int]map[int]int
// 迪克斯特拉
// graph:有向图
// start:起点
// end: 终点
// return:-1 没有路径 >=0 最短路径长度
func Dikstra(graph DirectedGraph,start int,end int) int {
// 通过map存储起点到指定点的最短路径 例如 {2:3} 表示起点到2这个点的最短距离是3
dstcMap := map[int]int{}
set := map[int]map[int]int{}
// 使用切片表示队列,用来有序遍历节点,首先把起点添加到队列中
q := []int{start}
// 遍历节点
for len(q) > 0 {
// 获取队列首部节点
node := q[0]
q = q[1:]
// 获取所有由节点出发的边集合
edges := graph[node]
// 遍历边集合
for n, w := range edges{
if _, ok := set[node][n]; ok {
continue
}
// 把边的终端节点添加到队列中
q = append(q, n)
if _, ok := set[node]; !ok {
set[node] = map[int]int{}
}
set[node][n] = 1
// 获取通过该边的路径长度
dstcNew := w
if _,ok := dstcMap[node];ok {
dstcNew += dstcMap[node]
}
// 如果求出的路径长度比通过其他边路径长度短,则覆盖
if dstc,ok := dstcMap[n];!ok || dstc > dstcNew {
dstcMap[n] = dstcNew
}
}
}
// 返回最短路径
if dstc,ok := dstcMap[end];ok {
return dstc
}
return -1
}
测试:
func main() {
directedGraph := DirectedGraph{1: {2: 1, 3: 3}, 2: {3: 1, 4: 2,5:1}, 3: {4: 1},5:{2:2}}
fmt.Println(Dikstra(directedGraph, 1, 4))
}
// map[2:1 3:2 4:3 5:2]
//3
对比结果,符合预期,到此一个初级的迪克斯特拉算法
就实现成功了