贪婪算法 — 单源最短路径Dijkstra算法

前言

本节的部分内容来自《算法导论》,因为《数据结构、算法和应用》这本书在算法设计部分注重于与前面所写的类的联系和实现。优点是贴近实际应用,但是在讲述单纯算法时,超级长的变量命名和注释却掩盖了算法最纯粹的思想。

算法和应用当然同等重要,但是要对算法有充分的理解才有应用的可能。

贪婪算法

单源最短路径Dijkstra算法

1)关于作者

在这里插入图片描述
以往我也尝试过寻找数据结构是谁提出的,比如栈和队列、堆等,可惜可能因为太过基础没有找到过。
这次的Dijsktra是老师们都非常喜欢介绍的例子,通过上面那段话,这个牛人是在不到半个小时内想出的算法,顺便知道了他还有未婚妻。
在这里插入图片描述
不仅如此,他还提出了“goto有害论”、提出信号量和PV原语并解决了操作系统中的“哲学家问题”。

2)问题描述

给定一个加权有向图G,它的每条边 ( i , j ) (i,j) 都有一个非负的成本 a [ i ] [ j ] a[i][j] 。一条路径的长度是该路径所有边成本之和。

目标:寻找一条从一个给定的源顶点出发到达其它任意一个顶点的最短路径

3)算法思想

Dijkstra在喝咖啡时,为了在两地来回更省时,最先想到的也是贪婪算法的思想。

如下图,假设我们要找到从节点1到节点5的最短路径。
在这里插入图片描述
贪婪准则:从一条最短路径还没有到达的顶点中,选择一个可以产生最短路径的目的节点。

怎么理解呢,我们假设源顶点为节点1,目标节点为顶点5。

我们设路径集P为该图中所有路径的集合,不管路径包含几条边,都包含在这个路径集中。

在路径集P中,我们分为几类路径子集, P 1 P_1 P 2 P_2 P 3 P_3 等等。 P 1 P_1 表示只含有一条边的路径, P 2 P_2 表示只含有两条边的路径依次类推。 P 2 P_2 中路径的长度不一定比 P 1 P_1 大,因为每条边的权值不同。

我们开始模拟算法:

  • 从顶点1开始,顶点1到自己的路径长度为0,表示我们已经找到顶点1到顶点1的最短路径。
  • 顶点1邻接2、3、5三个顶点,其中顶点3长度最短,我们在这一次遍历中找了顶点1到顶点3的最短路径。找到了顶点1到顶点2和顶点5暂时最短的路径 (这个地方可以暂停一下好好想想)。
  • 顶点1到顶点3是最短路径的原因是这三条边是仅有在 P 1 P_1 的路径,如果还有到达顶点3更短的路径,那么这个路径必定在 P 2 P_2 P 3 P_3 等等中。而任意包含多条边的路径,肯定有一条边属于 P 1 P_1 。而从顶点1到顶点3这条边又是 P 1 P_1 中最短的,所以顶点1到顶点3已经找到。实际上,从顶点1到顶点3这条路径是路径集P中所有路径中的最短路径。
  • 顶点1到顶点5是暂时最短的路径的原因可以接上一条,我们找到了一条在 P 1 P_1 中的路径但却不是最短的, P 2 P_2 P 3 P_3 这些多条边加起来的路径长度仍有可能比长度8要小,所以是暂时的最短路径。
  • 算法继续,找到顶点3后,我们通过顶点3的邻接节点可以找到顶点4暂时最短路径。
  • 为了保证我们不断有新的节点的最短路径被找到。我们寻找路径集P所有路径中第二短的路径。如果该路径的终点是一个除顶点1、3外的新节点,那么我们又找到了该新节点与顶点1的最短路径。
  • 第二短的路径我们应该在已经发现的存在路径的节点中寻找,目前我们已经确定顶点1、3找到了最短路径,顶点2、4、5存在长度为4、3、8的路径。第二短的路径就在这三个之中。
  • 原因是从顶点3出去的在 P 2 P_2 中的路径有可能比 P 1 P_1 中的路径长度短,从顶点2出去的路径不可能比从顶点1到顶点2的路径短,顶点5亦是如此。
  • 所以从顶点1到顶点4的最短路径长度为3,顶点4为找到最短路径的新节点。顶点4的邻接节点顶点5的暂时最短路径长度6也被找到。
  • 依次类推,路径集P中第三短的路径为从顶点1到顶点2长度为4的路径,顶点2的最短路径被找到。
  • 路径集P中第四短的路径为从顶点1->3->4->5长度为6的路径,顶点5的最短路径被找到,算法结束。

总之,贪婪准则可以保证我们每一次遍历邻接顶点后,可以找到一条所有路径中的最短路径,该路径的终点与源顶点的路径长度最短。也就是说,每次遍历,都会找到一个顶点与源顶点的最短路径(无环,若存在环则舍去新顶点为已找到最短路径顶点的情况)。

4)数据结构的选择

  1. 需要一个一维数组来保存顶点到源顶点的路径长度,初始化为无穷大。
  2. 需要一个一维数组来保存顶点所在最短路径的前驱顶点,以便输出路径。
  3. 需要一个数据结构来保证我们每次从未确定最短路径的顶点中提取出路径最短的顶点。在这里我们利用最小堆实现。

5)伪代码

在这里插入图片描述

6) 验证算法正确性

在这里插入图片描述

  • 如图所示,顶点s,u是已经找到最短路径的节点,路径S:s->u->v是利用Dijkstra找到的顶点v的最短路径。
  • 假设存在另一条路径,比路径S短,s->x->y->v为路径P。
  • 因为按照算法S被选中,而不是 P + e P^{'}+e ,所以S的长度小于等于 P + e P^{'}+e
  • 所以S的长度小于P
  • 与假设相反,即不存在比路径S更短的路径。

7)算法的复杂度

时间复杂度:
使用最小堆实现的时间复杂度为 O ( m l o g n ) O(mlogn) ,m为更改堆中元素路径长度的次数(Decrease-Key())。其实还可以继续优化,比如斐波那契堆,在这里我们不展开讨论。

如果使用数组来实现,那么时间复杂度为 O ( n 2 ) O(n^2)

猜你喜欢

转载自blog.csdn.net/qq_41882686/article/details/107880795