狄克斯特拉(Dijkstra)算法详解

1.前言

最近在看《算法图解》,其中第七章狄克斯特拉算法个人感觉并没有讲的清楚,比如看完7.1节给人的感觉是狄克斯特拉算法会遍历图中的每一条边,后续狄克斯特拉不适用负权边的说法就站不住脚了。后续在查阅诸多资料之后,总结文章一篇,尽可能以通俗易懂且思路清晰的方式来讲解狄克斯特拉算法。

2.简介

狄克斯特拉算法用于寻找在加权图中前往目标节点的最短路径,加权图是对边进行加权的图。

2.1.定理

设想这样一个场景——在一个没有负权边的有向图中,如果从起点直接到节点A的开销小于从起点直接到节点B的开销,那么即使从起点出发经过节点B还有其他路径可以到达节点A,其总开销也会大于从起点到节点A的开销。
狄克斯特拉(Dijkstra)算法详解
比如在上图中,起点到A的开销为3,这个开销一定小于从起点开始经其他节点到A的总开销,因为从起点到B的开销就已经大于从起点到A的开销。
上面推论的结果就是——最短路径的子路径仍然是最短路径 。比如在上图中,如果最短路径经过了A,那么在最短路径中从起点到A的子路径一定是所有从起点到A的路径中最短的那条。我们可以这样理解,在最短路径中,点A到终点的路径保持不变,如果不选择从起点到点A的所有路径中最短的那条,最后得到的路径就不是最短路径。同样的,我们可以保持起点到点A的路径不变,如果不选择从点A点到终点的所有路径中最短的那条,最后得到的路径也不是最短路径。
这就是为什么狄克斯特拉算法每次都从当前节点开销最少的邻居节点开始下一轮处理。

2.2.术语

3.算法步骤

狄克斯特拉算法可分为5步:

  1. 找出从起点出发,可以前往的、开销最小的未处理点
  2. 对于该节点的邻居,检查是否有前往他们的更短路径,如果有,则更新其开销
  3. 将该节点加入已处理队列中,后续不再处理该节点
  4. 重复1-4步,直到对图中除了终点的所有节点都进行了检查
  5. 得到最终路径

我们通过两个例子来体验上述步骤。

3.1.示例1

找出下图中,从起点到终点的最短路径
狄克斯特拉(Dijkstra)算法详解
准备一张表,存储从起点到各个节点的开销及其父节点,作用是方便最后找出最短路径和最短路径的长度。
初始化时,设起点开销为0,其他节点开销为 inf (无限大),算法结束的条件就是除终点外的所有节点都被处理了。

节点父节点开销是否已处理
起点0
Ainf
Binf
终点inf

找出当前开销最小的未处理节点,得到起点。起点的邻居是A和B,将其他不能直接到达的节点开销保持为 inf ,更新表:

节点父节点开销是否已处理
起点0
A起点6
B起点2
终点inf

继续找出当前开销最小的未处理节点,得到节点B,计算经节点B前往其各个邻居的开销:

更新表:

节点父节点开销是否已处理
起点0
AB5
B起点2
终点B7

至此B节点的所有邻居处理完毕,将B标识为已处理:

节点父节点开销是否已处理
起点0
AB5
B起点2
终点B7

重复上面的过程,找出当前开销最小的未处理节点,得到A,计算经节点A前往其各个邻居的开销:

更新表:

节点父节点开销是否已处理
起点0
AB5
B起点2
终点A6

至此A节点的所有邻居处理完毕,将A标识为已处理:

节点父节点开销是否已处理
起点0
AB5
B起点2
终点A6

到此所有除了终点的节点都经过检查,得出从起点到终点最短距离为6,最短路径通过终点的父节点逐级向上追溯为:起点 -> B -> A -> 终点。

3.2.示例2

示例2由示例1改变节点B到节点A路径的方向而来,通过该示例可以更好的理解狄克斯特拉算法的处理过程。
狄克斯特拉(Dijkstra)算法详解
同样的操作,准备一张表:

节点父节点开销是否已处理
起点0
Ainf
Binf
终点inf

找出当前开销最小的未处理节点,得到起点,起点的邻居是A和B,更新表:

节点父节点开销是否已处理
起点0
A起点6
B起点2
终点inf

找出当前开销最小的未处理节点,得到节点B,更新表:

节点父节点开销是否已处理
起点0
A起点6
B起点2
终点B7

找出下一个开销最小的未处理节点,得到节点A,对节点A的处理就和示例1有所不同了。
节点A有两个邻居:节点B和终点。但节点B已经处理过了,这意味着当前由起点前往节点B的开销最少的路径已经被发现了,结合之前的定理,这意味着两点:

综上,不必要检查 A-> B 这条路径的开销,只需要检查 A -> 终点 这条路径的开销。
由于 起点 -> A -> 终点 这条路径的开销没有小于 7,所以不更新A邻居节点的开销,只将A置为已处理即可:

节点父节点开销是否已处理
起点0
A起点6
B起点2
终点B7

到此所有除了终点的节点都经过检查,得出从起点到终点最短距离为7,最短路径通过终点的父节点逐级向上追溯为:起点 -> B -> 终点
需要注意的是,这次我们只找到了其中一条最短路径,因为 起点 -> A -> 终点 这条路径的开销也是 7。

4.实现

下面用python展示从下图中找出用乐谱交换钢琴需要额外支付费用最少的路径
狄克斯特拉(Dijkstra)算法详解

# 使用狄克斯特拉在加权有向图中寻找最短路径
from cmath import inf

# 构建图
map = {}
map['乐谱'] = {}
map['乐谱']['唱片'] = 5
map['乐谱']['海报'] = 0
map['唱片'] = {}
map['唱片']['吉他'] = 15
map['唱片']['架子鼓'] = 20
map['海报'] = {}
map['海报']['吉他'] = 30
map['海报']['架子鼓'] = 35
map['吉他'] = {}
map['吉他']['钢琴'] = 20
map['架子鼓'] = {}
map['架子鼓']['钢琴'] = 10

# 初始化table中的行
def initRow(costs, key, cost):
  costs[key] = {}
  costs[key]['father'] = None
  costs[key]['cost'] = cost
  costs[key]['isChecked'] = False

# 获取当前开销最少的节点
def findLowerCostNode(costs):
  key = None
  cost = float(inf)

  for name in costs:
    item = costs[name]
    if (item['isChecked'] == False and item['cost'] < cost ):
      cost = item['cost']
      key = name

  return key

def dijkstra(map, start, end):
  # 初始化表
  costs = {}
  initRow(costs, start, 0)

  key = findLowerCostNode(costs)

  while (key != None and key != end): 
    # 获取邻居
    neighbors = map[key].keys()

    # 检查到邻居的开销
    for neighbor in neighbors:
      # 如果邻居不在costs中,则添加
      if (neighbor not in costs):
        initRow(costs, neighbor, float(inf))

      # 获取到key的开销
      newCost = map[key][neighbor] + costs[key]['cost']
      # 如果经key到邻居的开销小于已有开销
      if (newCost < costs[neighbor]['cost']):
        costs[neighbor]['cost'] = newCost
        costs[neighbor]['father'] = key
    
    # key的所有邻居都检查过了
    costs[key]['isChecked'] = True
    key = findLowerCostNode(costs)

  # 打印结果
  key = end
  routes = []

  while (key != None):
    node = costs[key]
    routes.insert(0, key)
    father = node['father']
    key = father
    
  print(f'最短路径为: {"->".join(routes)},开销{costs[end]["cost"]}')
  # print(costs)

dijkstra(map, '乐谱', '钢琴')

5.负权边

负权边即权重为负的边,如果有负权边,则不能使用狄克斯特拉算法,而应该使用贝尔曼-福德算法。
狄克斯特拉(Dijkstra)算法详解
还是用这张图举例,如果X、Y、Z中有负数,那经过B到的A的开销可能就比3小了,即使从起点到B的开销大于从起点到A开销。

6.参考

图最短路径算法之迪杰斯特拉算法(Dijkstra)

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年3月9日
下一篇 2023年3月9日

相关推荐

此站出售,如需请站内私信或者邮箱!