会一会改变世界的图算法——Dijkstra(狄克斯特拉)算法

小序

最近在看《算法图解》这本书,对【狄克斯特拉算法】这一章很有感触。javascript

狄克斯特拉算法是很是著名的算法,是改变世界的十大算法之一,用于解决【赋权】【有向无环图】的【单源最短路径】问题java

若是没有这种算法,因特网确定没有如今的高效率。只要能以“图”模型表示的问题,都能用这个算法找到“图”中两个节点间的最短距离。狄克斯特拉算法的稳定性至今仍没法被取代。node

注:狄克斯特拉算法的原始版本仅适用于找到两个顶点之间的最短路径,后来更常见的变体固定了一个顶点做为源结点而后找到该顶点到图中全部其它结点的最短路径,产生一个最短路径树(树是没有环的图)。本文讨论的是后者。git

定义

若是觉着序言中加红标粗的这句释义难理解?让咱一一拆解,您就明白了。假若知晓概念,可选跳过此节。github

何为图

  • 图由【节点】和【】组成,用来模拟不一样东西的链接关系。

图 1-1面试

咱们发现咱们太多的现实场景都与图这种结构相关。人与人之间的关联,地点与地点之间的关联,各种拓扑图等。后文会例举具体场景案例。算法

何为有向无环图

何为有向?数组

图 1-1 是无向图,而图 1-2 则是有向图,区别在于后者标注了点与点之间关联方向。markdown

图 1-2数据结构

何为无环?

若是一个有向图从任意顶点出发没法通过若干条边回到该点,则这个图是一个有向无环图。

  • Q&A

Q:图 1-2 是有向无环的吗?

A:不是,由于 A 通过 C 以后又回到了 A。

图 1-3

那图 1-3 是有向无环的吗?

答:是的,欲知更多在 zh.wikipedia.org/wiki/有向无环图

何为赋权

这里的“权”即“权重”,“赋权”便是给图的边赋权重值。

图 1-4

好比图 1-4 从点 1 到点 2,须要走 10 步,从点 1 到点 5 须要 100 步,这里的 10 和 100 即为“权重值”。

特注:Dijkstra 算法边权非负。

何为单源最短路径

最短路径是计算给定的两个节点之间最短(最小权重)的路径,若是起点肯定,则叫单源最短路径。

最短路径有不少现实应用:不少地图均提供了导航功能,它们就使用了最短路径算法或其变种。咱们在不少社交平台上查看某人的简介时,平台会展现大家之间有多少共同好友,并列出之间的关系,也是基于此算法。

咱们如今在回看这句定义:

狄克斯特拉算法用于解决【赋权】【有向无环图】的【单源最短路径】问题

您是否明了?只需紧扣“赋权”、“有向无环图”、“单源最短路径”这三个关键词。粗犷点讲,这个算法就是用于找两点之间的最短距离的。

实现

那么重点来了,狄克斯特拉算法究竟是怎样实现的呢?

回到《算法图解》一书,咱们能够看到最直观的例子。

图 2-1

在图 2-1 中,从起点到终点的最短路径是多少呢?

若是您使用广度优先搜索(BFS),获得的答案将是 7(具体实现,按下不表),但这明显不是最优解。咱们能够人眼识别,看出正确答案应该是 6,即从起点 —— 到 B 点 —— 到 A 点 —— 到终点。

若是经过计算机,正确答案是怎么算出来的呢?正是我们的主角——狄克斯特拉算法

四步走

狄克斯特拉算法包括 4 个步骤:

  1. 找出“最便宜”的节点,便可在最短期内到达的节点。
  2. 更新该节点的邻居的开销,其含义将稍后介绍。
  3. 重复这个过程,直到对图中的每一个节点都这样作了。
  4. 计算最终路径。
  • 第一步:找出“最便宜”节点

咱先看第一步,你起点,有两条路可选,去到 A 需 6 步,去到 B 需 2 步,先无论其它节点,B 点即最便宜节点 记录如下集合,这点很是重要。

图 2-2 图 2-3

  • 第二步:计算通过节点 B 前往各个邻居所需时间。

起点通过 B 点 到 A 需 5 步,起点通过 B 点 到终点需 7 步,以前的集合中起点到 A 点须要 6 步,到终点是正无穷,如今有了更优解,则须要更新该开销集合,得出图 2-4。

图 2-3 图 2-4

  • 第三步:重复!!!

如何重复?咱们已经基于 B 点作了更新操做,咱们须要对剩下节点作相似的操做。图 2-4 表中,除了 B ,A 点的开销最小,因此咱们须要对 A 点开刀了。—— “更新节点 A 全部邻居的开销。

起点通过 A 点到终点须要 1 步,5 + 1 = 6 ,小于图 2-4 中终点开销所需值 7,咱们应该更新开销集合。

图 2-5

咱们对每一个节点都采用了狄克斯特拉算法(无需对终点这样作),因此图 2-5 是最后的开销集合,也是最终最优解。从起点到终点最少只需 6 步!

  • 第四步?

细心的朋友可能发现了,说好的四步呢?上面怎么只有三步?这里做者在留了个“心机”,其实上面的例子只是算出了最小的开销的值,并未得出实现最小开销的最终路径,即缺乏了一个回溯的过程。

如何计算最终路径?做者这里又举了一个例子,且此例要更为复杂一些。不过本瓜认为:狄克斯特拉算法的核心在于第二步、第三步(开销数组的更新),第四步得出具体路径只是增长一个父子关系进行回溯补充。

图 2-6

如图 2-6 ,问:从曲谱到钢琴的最短路径是多少?

答案是: 曲谱 —— 唱片 —— 架子鼓 —— 钢琴,你知道其中开销集合的具体更新过程吗?我想有人面试应该遇到过这题。了解更多

本瓜简述:由点【曲谱】出发,相邻【唱片】和【海报】两点,将它们放到开销数组中,值分别为 0 和 5。0 小于 5,因此基于【海报】,执行第二步,拿到【曲谱】经过【海报】达到其相邻的点的值,分别是【吉他】30 和【架子鼓】35,此时开销数组里面有四个值:

名称 开销
海报 0(已遍历相邻值)
唱片 5
吉他 30
架子鼓 35
... ...

5<30<35,进行重复操做,以【唱片】为基础,拿到【曲谱】到它相邻的点的值。分别为【吉他】20,【架子鼓】25,都小于开销数组中的值,进行更新。此时的开销数组为:

名称 开销
海报 0(已遍历相邻值)
唱片 5(已遍历相邻值)
吉他 20
架子鼓 25
... ...

继续遍历,20 < 25,此时应该基于【吉他】,【吉他】与钢琴相连,【曲谱】经过【唱片】到【吉他】再到【钢琴】,需 40,更新数组。25 < 40,再基于【架子鼓】遍历,架子鼓也只和【钢琴】相连,【曲谱】——【唱片】——【架子鼓】——【钢琴】,值为 35,35 小于 40 ,更新。最终只有【钢琴】这一点没遍历,而【钢琴】又是终点,则执行结束啦。最终是:

名称 开销
海报 0(已遍历相邻值)
唱片 5(已遍历相邻值)
吉他 20(已遍历相邻值)
架子鼓 25(已遍历相邻值)
钢琴 35(终点,无需遍历)

能轻松过一遍,算法思想就没啥问题啦~

其实,最短路径不必定是物理距离,也能够转化其它度量指标,好比钱、时间等等。将生活中的场景抽象成此类算法问题,妈妈不再用担忧我走弯路了~

狄克斯特拉!牛!

致敬此算法的做者 —— Edsger Wybe Dijkstra,他在1972年得到图灵奖。

代码

算法思想很重要,但 TALK IS CHEAP!! 这里用 py 实现。同时也找到一篇 JS 实现-Finding the Shortest Path in Javascript: Dijkstra’s Algorithm 挖个坑,有空翻译。/(ㄒoㄒ)/~~

node = find_lowest_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_lowest_cost_node(costs) // 找出接下来要处理的节点,并循环

// 找出开销最低的节点
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
复制代码

costs 数组即为开销数组,能够获得最小开销,也就是最短路径。

有兴趣也可看北大屈婉玲教授的视频——《单源最短路径问题及算法》,讲的很是清晰。

迷思

美丽心灵

狄克斯特拉算法其实是一个贪婪算法。由于该算法老是试图优先访问每一步循环中距离起始点最近的下一个结点。

本瓜正好最近在看一部电影——《美丽心灵》,又加深了对“纳什均衡”的认知。

在博弈论中,纳什均衡(英语:Nash equilibrium,或称纳什均衡点)是指在包含两个或以上参与者的非合做博弈(Non-cooperative game)中,假设每一个参与者都知道其余参与者的均衡策略的状况下,没有参与者能够透过改变自身策略使自身受益时的一个概念解。—— 维基百科

在一个博弈过程当中,不管对方的策略选择如何,当事人一方都会选择某个肯定的策略,则该策略被称做支配性策略。若是任意一位参与者在其余全部参与者的策略肯定的状况下,其选择的策略是最优的,那么这个组合就被定义为纳什平衡。—— 百度百科

两者综合,本瓜产生了困惑:

在这个狄克斯特拉算法中,咱们每走一步都是一次博弈。若是将每一步的博弈交给不一样的人去作,都达到自身的最优解,那么最终的解是否必定是最优的呢......?这涉及算法的稳定性?仍是概念混淆了,仍是有点哲学那味了?Anyway, 这东西还挺有意思的。算法、博弈论、最优解......

概念整理

  • 图算法

“在我所知道的算法中,图算法应该是最有用的”。—— Aditya Bhargava(《算法图解》做者)

图算法有三类核心:路径搜索、中心性计算、社群发现。

图算法还有最基础的两个遍历算法

  1. 广度优先搜索(BFS)
  2. 深度优先搜索(DFS)

学过《数据结构》的应该都不陌生。同时,BFS 能够拿出与狄克斯特拉算法作对比,前者可用于在非加权图中查找最短路径,后者用于加权图中。还要提一嘴的是,若是图的权为负数,要使用【贝尔曼-福德算法】。有兴趣再拓展⑧。

工具

以上

撰文不易,还需鼓励。年轻人,讲点武德 ~ 喜欢就点赞,反感就三连。谢谢~

我是掘金安东尼,一位持续输出的我的站长~

相关文章
相关标签/搜索