《算法图解》第七章迪克斯特拉学习心得

1、狄克斯特拉算法 (Dijkstra's algorithm

对于广度优先搜索找出的路径,只适用于图中边的权值都相同的情况,如果权值不相同,可能找出的未必是最短路径。如图:


对于不同权值的图,我们使用狄克斯特拉算法寻找最短路径,该算法包含以下四个步骤:

  • 找出最便宜的节点, 即可在最短时间内到达的节点。
  • 更新该节点的邻居的开销, 其含义将稍后介绍。
  • 重复这个过程, 直到对图中的每个节点都这样做了。
  • 计算最终路径。

2、术语

狄克斯特拉算法用于每条边都有关联数字的图, 这些数字称为权重(weight带权重的图称为加权图 (weighted graph , 不带权重的图称为非加权图 (unweighted graph 要计算非加权图中的最短路径, 可使用广度优先搜索 。 要计算加权图中的最短路径, 可使用狄克斯特拉算法 。 图还可能有环 , 而环类似下图。

狄克斯特拉算法只适用于有向无环图 (directed acyclic graphDAG

3、算法举例

  • 问题背景

Rama, 想拿一本乐谱换架钢琴。Alex说: 这是我最喜欢的乐队Destroyer的海报, 我愿意拿它换你的乐谱。 如果你再加5美元, 还可拿乐谱换我这张稀有的Rick Astley黑胶唱片。 ”Amy说: 哇, 我听说这张黑胶唱片里有首非常好听的歌曲, 我愿意拿我的吉他或架子鼓换这张海报或黑胶唱片。 Beethoven惊呼: 我一直想要吉他, 我愿意拿我的钢琴换Amy的吉他或架子鼓。  下图为他们的交换意愿:



这个图中的节点是大家愿意拿出来交换的东西, 边的权重是交换时需要额外加多少钱。Rama需要确定采用哪种路径将乐谱换成钢琴时需要支付的额外费用最少。 为此, 可以使用狄克斯特拉算法! 别忘了, 狄克斯特拉算法包含四个步骤。 在这个示例中, 你将完成所有这些步骤, 因此你也将计算最终路径。

首先创建一个表格, 在其中列出每个节点的开销。 这里的开销指的是达到节点需要额外支付多少钱。

在执行狄克斯特拉算法的过程中, 你将不断更新这个表。 为计算最终路径, 还需在这个表中添加表示父节点的列。

第一步 : 找出最便宜的节点。 在这里, 换海报最便宜,不需要支付额外的费用。 
第二步 : 计算前往该节点的各个邻居的开销。

表中增加了架子鼓和吉他的开销,这些开销是通过海报交换所需支付的额外费用,所以他们的父节点均为海报。

再次执行第一步 : 下一个最便宜的节点是黑胶唱片——需要额外支付5美元。
再次执行第二步 : 更新黑胶唱片的各个邻居的开销。

对照上表,我们发现吉他和架子鼓的开销都更新了,这意味着经黑胶唱片前往架子鼓吉他的开销更低, 因此你将这些乐器的父节点改为黑胶唱片。

下一个最便宜的是吉他, 因此更新其邻居的开销。

于是,你计算出了到达钢琴的开销——40,而它的父节点为吉他。

最后, 对最后一个节点——架子鼓, 做同样的处理。


如果用架子鼓换钢琴, Rama需要额外支付的费用更少。 因此, 采用最便宜的交换路径时, Rama需要额外支付35美元 。

最短路径的确定:找到钢琴的父节点(架子鼓),通过沿父节点回溯, 便得到了完整的交换路径。


注意:不能将狄克斯特拉算法用于包含负权边的图 。 在包含负权边的图中, 要找出最短路径, 可使用另一种算法——贝尔曼-福德算法 (Bellman-Ford algorithm) 。 

4、算法实现


python代码实现:

graph={}
#储存起点的邻居和权值
graph["start"]={}
graph["start"]["a"]=6
graph["start"]["b"]=2
#添加其他节点和邻居
graph["a"]={}
graph["a"]["fin"]=1

graph["b"]={}
graph["b"]["a"]=3
graph["b"]["fin"]=5

graph["fin"]={}#终点没有任何邻居
#储存父节点
parents={}
parents["a"]="start"
parents["b"]="start"
parents["fin"]=None

#记录处理过的节点
processed=[]
#储存每个节点的开销
infinity=float("inf")
costs={}
costs["a"]=6
costs["b"]=2
costs["fin"]=infinity
def find_lower_cost_node(costs):
    lowest_cost=float("inf")
    lowest_cost_node=None
    for node in costs:#遍历所有的节点
        cost=costs[node]
        if cost<lowest_cost and node not in processed:#如果当前节点的开销更低且未处理过
            lowest_cost=cost
            lowest_cost_node=node#就将其视为开销最低的节点
    return lowest_cost_node
node=find_lower_cost_node(costs)#在未处理的节点中找出开销最小的节点
while node is not None:#这个while循环在所有节点都被处理过后结束
    cost=costs[node]
    neighbors=graph[node]
    for n in neighbors.keys():#遍历当前节点的所有邻居
        new_cost=cost+neighbors[n]
        if costs[n]>new_cost:#如果经当前节点前往该邻居更近
            costs[n]=new_cost#就更新该邻居的开销
            parents[n]=node#同时将该邻居的父节点设置为当前节点
    processed.append(node)#将当前节点标记为处理过
    node=find_lower_cost_node(costs)#找出接下来要处理的节点, 并循环

结果输出:


5、小结

  • 广度优先搜索用于在非加权图中查找最短路径。
  • 狄克斯特拉算法用于在加权图中查找最短路径。
  • 仅当权重为正时狄克斯特拉算法才管用。
  • 如果图中包含负权边, 请使用贝尔曼-福德算法。




  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值