加权图最短路径 - 迪克斯特拉算法

问题

假设地图上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

对比结果,符合预期,到此一个初级的迪克斯特拉算法就实现成功了

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值