算法图解part7:狄克斯特拉算法

1.狄克斯特拉算法(Dijkstra’s algorithm)

  • 广度优先搜索来查找两点之间的最短路径,那时“最短路径”的意思是段数最少
  • 在狄克斯特拉算法中,你给每段都分配了一个数字或权重,因此狄克斯特拉算法找出的是总权重最小的路径。

百度百科:
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

2.术语

该算法用于每条边都有关联数字的图,这些数字称为权重(weight)
在这里插入图片描述
带权重的图为加权图(weighted graph),不带权重的图为非加权图(unweighted graph)
在这里插入图片描述

  • 计算非加权图中的最短路径,可使用广度优先搜索。计算加权图中的最短路径,可使用狄克斯特拉算法。
  • 可能有的存在:绕环的路径不可能是最短的路径,在无向图中,每条边都是一个环。狄克斯特拉算法只适合于有向无环图(directed acyclic graph,DAG)

3.负权边

不能将狄克斯特拉算法用于包含负权边的图。

狄克斯特拉算法为什么不能应用于负权边呢?此处请参考:https://blog.csdn.net/baidu_31818237/article/details/50611592

在包含负权边的图中,要找出最短路径,可使用另一种算法——贝尔曼-福德算法(Bellman-Fordalgorithm)。

百度百科:
贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解单源最短路径问题的一种算法。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪科斯彻算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。但算法可以进行若干种优化,提高了效率。

松弛操作:
松弛操作是指对于每个顶点v∈V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计(shortest-path estimate)。

贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替代。 然而,迪科斯彻算法以贪心法选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作; 而贝尔曼-福特算法简单地对所有边进行松弛操作,共 (V-1) 次,其中 V 是图的点的数量。在重复地计算中,已计算得到正确的距离的边的数量不断增加,直到所有边都计算得到了正确的路径。这样的策略使得贝尔曼-福特算法比迪科斯彻算法适用于更多种类的输入。

贝尔曼-福特算法的最多运行 O(VE)次,(V和 E分别是节点和边的数量)。
V为(vertice),E为边数(edge)。

4.实现狄克斯特拉算法

算法目标:找出一个图中最快(耗时最短)的路径。

实现步骤:

  • 1.找出最短时间内前往的节点;
  • 2.对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销;
  • 3.重复这个过程,直到对图中的每个节点都重复了以上两个步骤;
  • 4.计算最终路径。

4.1 最短路径思路

以下图为例:
在这里插入图片描述
第一步: 找出最便宜的节点,你站在起点,不知道该前往节点A还是节点B。前往节点A需要6分钟,前往节点B需要2分钟,由于还不知道前往终点需要多长时间,因此假设为无穷大。
在这里插入图片描述
第二步: 计算经节点B前往其各个邻居所需的时间。
在这里插入图片描述
对于节点B的邻居,如果找到前往它的更短路径,就更新其开销。在这里,我们找到了:

  • 前往节点A的更短路径(时间从6分钟缩短到5分钟)。

  • 前往终点的更短路径(时间从无穷大缩短到7分钟)。

第三步: 重复!
现在更新节点A的所有邻居的开销。这时前往终点的时间缩短到6分钟。
在这里插入图片描述
第四步: 计算最终路径。

最短路径不断寻找父节点,6 = 1 + 5 ,5 = 2 + 3

4.2 python实现代码

要解决这个问题,需要先画出三个散列表:
在这里插入图片描述
随着算法的进行,我们将不断更新散列表costs和parents。

PS: costs指节点开销图(节点 | 开销),parents指父节点图(节点 | 父节点)。

graph = {}  #首先需要实现这个图

需要同时存储邻居和前往邻居的开销

graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2

在这里插入图片描述
同时还需要用一个散列表来存储每个节点的开销costs,一个存储父节点的散列表parents,一个数组processed(用于记录处理过的节点。因为对于同一个节点,不用处理多次。) 。
在这里插入图片描述

# 存储每个节点开销的散列表
infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity

# 存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

processed = []  # 一个数组,用于记录处理过的节点。因为对于同一个节点,不用处理多次。

下面来看看算法的执行过程:
在这里插入图片描述

# 添加节点和邻居
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"] = {}  # 终点没有邻居

# 存储每个节点开销的散列表
infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity

# 存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

processed = []  # 一个数组,用于记录处理过的节点。因为对于同一个节点,不用处理多次。

def find_lowest_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_lowest_cost_node(costs)
# 这个while循环在所有节点都被处理过后结束
while node is not None:
    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_lowest_cost_node(costs)

print ("Cost from the start to each node:")
print (costs)

运行结果:

Cost from the start to each node:
{‘a’: 5, ‘b’: 2, ‘fin’: 6}

5.总结

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

6.参考资料

《算法图解》第七章:详细的程序思路介绍

松弛操作:https://baike.baidu.com/item/松弛操作/7554089

此部分学习算法内容已上传github:https://github.com/ShuaiWang-Code/Algorithm/tree/master/Chapter7

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值