A. 图遍历算法之最短路径Dijkstra算法
最短路径问题是图论研究中一个经典算法问题,旨在寻找图中两节点或单个节点到其他节点之间的最短路径。根据问题的不同,算法的具体形式包括:
常用的最短路径算法包括:Dijkstra算法,A 算法,Bellman-Ford算法,SPFA算法(Bellman-Ford算法的改进版本),Floyd-Warshall算法,Johnson算法以及Bi-direction BFS算法。本文将重点介绍Dijkstra算法的原理以及实现。
Dijkstra算法,翻译作戴克斯特拉算法或迪杰斯特拉算法,于1956年由荷兰计算机科学家艾兹赫尔.戴克斯特拉提出,用于解决赋权有向图的 单源最短路径问题 。所谓单源最短路径问题是指确定起点,寻找该节点到图中任意节点的最短路径,算法可用于寻找两个城市中的最短路径或是解决着名的旅行商问题。
问题描述 :在无向图 中, 为图节点的集合, 为节点之间连线边的集合。假设每条边 的权重为 ,找到由顶点 到其余各个节点的最短路径(单源最短路径)。
为带权无向图,图中顶点 分为两组,第一组为已求出最短路径的顶点集合(用 表示)。初始时 只有源点,当求得一条最短路径时,便将新增顶点添加进 ,直到所有顶点加入 中,算法结束。第二组为未确定最短路径顶点集合(用 表示),随着 中顶点增加, 中顶点逐渐减少。
以下图为例,对Dijkstra算法的工作流程进行演示(以顶点 为起点):
注:
01) 是已计算出最短路径的顶点集合;
02) 是未计算出最短路径的顶点集合;
03) 表示顶点 到顶点 的最短距离为3
第1步 :选取顶点 添加进
第2步 :选取顶点 添加进 ,更新 中顶点最短距离
第3步 :选取顶点 添加进 ,更新 中顶点最短距离
第4步 :选取顶点 添加进 ,更新 中顶点最短距离
第5步 :选取顶点 添加进 ,更新 中顶点最短距离
第6步 :选取顶点 添加进 ,更新 中顶点最短距离
第7步 :选取顶点 添加进 ,更新 中顶点最短距离
示例:node编号1-7分别代表A,B,C,D,E,F,G
(s.paths <- shortest.paths(g, algorithm = "dijkstra"))输出结果:
(s.paths <- shortest.paths(g,4, algorithm = "dijkstra"))输出结果:
示例:
找到D(4)到G(7)的最短路径:
[1] 维基网络,最短路径问题: https://zh.wikipedia.org/wiki/%E6%9C%80%E7%9F%AD%E8%B7%AF%E9%97%AE%E9%A2%98 ;
[2]CSDN,Dijkstra算法原理: https://blog.csdn.net/yalishadaa/article/details/55827681 ;
[3]RDocumentation: https://www.rdocumentation.org/packages/RNeo4j/versions/1.6.4/topics/dijkstra ;
[4]RDocumentation: https://www.rdocumentation.org/packages/igraph/versions/0.1.1/topics/shortest.paths ;
[5]Pypi: https://pypi.org/project/Dijkstar/
B. dijkstra算法是什么
Dijkstra算法是由荷兰计算机科学家狄克斯特拉(Dijkstra)于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。
其基本原理是:每次新扩展一个距离最短的点,更新与其相邻的点的距离。当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,因而保证了算法的正确性。
不过根据这个原理,用Dijkstra求最短路的图不能有负权边,因为扩展到负权边的时候会产生更短的距离,有可能就破坏了已经更新的点距离不会改变的性质。
举例来说,如果图中的顶点表示城市,而边上的权重表示着城市间开车行经的距离。Dijkstra算法可以用来找到两个城市之间的最短路径。
Dijkstra算法的输入包含了一个有权重的有向图G,以及G中的一个来源顶点S。我们以V表示G中所有顶点的集合。每一个图中的边,都是两个顶点所形成的有序元素对。(u,v)表示从顶点u到v有路径相连。我们以E所有边的集合,而边的权重则由权重函数w: E→[0,∞]定义。
因此,w(u,v)就是从顶点u到顶点v的非负花费值(cost)。边的花费可以想象成两个顶点之间的距离。任两点间路径的花费值,就是该路径上所有边的花费值总和。
已知有V中有顶点s及t,Dijkstra算法可以找到s到t的最低花费路径(i.e.最短路径)。这个算法也可以在一个图中,找到从一个顶点s到任何其他顶点的最短路径。
C. 最短路径算法(Dijkstra)
Dijkstra( 迪科斯特拉 )算法是用来解决核激唯单源最短路径的算法,要求路径权值非负数。该算法利用了深度优先搜索和贪心的算法。
下面是一个有权图,求从A到各个节点的最短路径。
第1步:从A点出发,判断每个点到A点的路径(如果该点不能直连A点则距离值为无穷大,如果该点能和A直连则是当前的权值),计算完之后把A点上色,结果如下图:
第2步:从除A点之外的点查找到距离A点最近的点C,从C点出发查找其邻近的节点(除去已上色的点),并重新计算C点的邻近点距离A点的值,如图中B点,若新值(C点到A点的值+C点到该点的路径)小于原值,则将值更新为5,同理更新D、E点。同时将C标铅陵记为已经处理过,如图所示涂色。
第3步:从上色的节点中查找距离A最近的B点,重复第3步操作。
第4步: 重复第3步,改培2步,直到所有的节点都上色。
最后就算出了从A点到所有点的最短距离。
leetcode 743题
D. Dijkstra算法
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。注意该算法要求图中不存在负权边。
设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度含侍仿。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
(1)初始时,S只包含起点D;U包含除D外的其他顶点,且U中顶点的距离为“起点D到该顶点的距离”(例如,U中顶点A的距离为[D,A]的长度,然后D和A不相邻,则谈枣A的距离为∞)
(2)从U中选出“距离最短的顶点K”,并将顶点K加入到S中;同时,从U中移除顶点K
(3)更新U中各个顶点到起点D的距离。之所以更新U中顶点的距离,是由于上一步谈纤中确定了K是求出最短路径的顶点,从而可以利用K来更新其他顶点到起点D的距离(例如,[D,A]的距离可能大于[D,K]+[K,A]的距离)
(4)重复步骤(2)和(3),直到遍历完所有顶点
https://blog.csdn.net/yalishadaa/article/details/55827681
E. 最短路径的Dijkstra算法
Dijkstra算法(迪杰斯特拉)是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。可以用堆优化。
Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。
Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表方式,Drew为了和下面要介绍的 A* 算法和 D* 算法表述一致,这里均采用OPEN,CLOSE表的方式。
其采用的是贪心法的算法策略
大概过程:
创建两个表,OPEN, CLOSE。
OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。
1. 访问路网中距离起始点最近且没有被检查过的点,把这个点放入OPEN组中等待检查。
2. 从OPEN表中找出距起始点最近的点,找出这个点的所有子节点,把这个点放到CLOSE表中。
3. 遍历考察这个点的子节点。求出这些子节点距起始点的距离值,放子节点到OPEN表中。
4. 重复第2和第3步,直到OPEN表为空,或找到目标点。 #include<iostream>#include<vector>usingnamespacestd;voiddijkstra(constint&beg,//出发点constvector<vector<int>>&adjmap,//邻接矩阵,通过传引用避免拷贝vector<int>&dist,//出发点到各点的最短路径长度vector<int>&path)//路径上到达该点的前一个点//负边被认作不联通//福利:这个函数没有用任何全局量,可以直接复制!{constint&NODE=adjmap.size();//用邻接矩阵的大小传递顶点个数,减少参数传递dist.assign(NODE,-1);//初始化距离为未知path.assign(NODE,-1);//初始化路径为未知vector<bool>flag(NODE,0);//标志数组,判断是否处理过dist[beg]=0;//出发点到自身路径长度为0while(1){intv=-1;//初始化为未知for(inti=0;i!=NODE;++i)if(!flag[i]&&dist[i]>=0)//寻找未被处理过且if(v<0||dist[i]<dist[v])//距离最小的点v=i;if(v<0)return;//所有联通的点都被处理过flag[v]=1;//标记for(inti=0;i!=NODE;++i)if(adjmap[v][i]>=0)//有联通路径且if(dist[i]<0||dist[v]+adjmap[v][i]<dist[i])//不满足三角不等式{dist[i]=dist[v]+adjmap[v][i];//更新path[i]=v;//记录路径}}}intmain(){intn_num,e_num,beg;//含义见下cout<<输入点数、边数、出发点:;cin>>n_num>>e_num>>beg;vector<vector<int>>adjmap(n_num,vector<int>(n_num,-1));//默认初始化邻接矩阵for(inti=0,p,q;i!=e_num;++i){cout<<输入第<<i+1<<条边的起点、终点、长度(负值代表不联通):;cin>>p>>q;cin>>adjmap[p][q];}vector<int>dist,path;//用于接收最短路径长度及路径各点dijkstra(beg,adjmap,dist,path);for(inti=0;i!=n_num;++i){cout<<beg<<到<<i<<的最短距离为<<dist[i]<<,反向打印路径:;for(intw=i;path[w]>=0;w=path[w])cout<<w<<<-;cout<<beg<<'
';}}
F. 最短路径 | 深入浅出Dijkstra算法(一)
上次我们介绍了神奇的只有 五行的 Floyd-Warshall 最短路算法 ,它可以方便的求得 任意两点的最短路径, 这称为 “多源最短路”。
这次来介绍 指定一个点(源点)到其余各个顶点的最短路径, 也叫做 “单源最短路径”。 例如求下图中的 1 号顶点到 2、3、4、5、6 号顶点的最短路径。
与 Floyd-Warshall 算法一样,这里仍然 使用二维数组 e 来存储顶点之间边的关系, 初始值如下。
我们还需要用 一个一维数组 dis 来存储 1 号顶点到其余各个顶点的初始路程, 我们可以称 dis 数组为 “距离表”, 如下。
我们将此时 dis 数组中的值称为 最短路的“估计值”。
既然是 求 1 号顶点到其余各个顶点的最短路程, 那就 先找一个离 1 号顶点最近的顶点。
通过数组 dis 可知当前离 1 号顶点最近是 2 号顶点。 当选择了 2 号顶点后,dis[2]的值就已经从“估计值”变为了“确定值”, 即 1 号顶点到 2 号顶点的最短路程就是当前 dis[2]值。
为什么呢?你想啊, 目前离 1 号顶点最近的是 2 号顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 1 号顶点到 2 号顶点的路程进一步缩短了。 因此 1 号顶点到其它顶点的路程肯定没有 1 号到 2 号顶点短,对吧 O(∩_∩)O~
既然选了 2 号顶点,接下来再来看 2 号顶点 有哪些 出边 呢。有 2->3 和 2->4 这两条边。
先讨论 通过 2->3 这条边能否让 1 号顶点到 3 号顶点的路程变短。 也就是说现在来比较 dis[3] 和 dis[2]+e[2][3] 的大小。其中 dis[3]表示 1 号顶点到 3 号顶点的路程,dis[2]+e[2][3]中 dis[2]表示 1 号顶点到 2 号顶点的路程,e[2][3]表示 2->3 这条边。所以 dis[2]+e[2][3]就表示从 1 号顶点先到 2 号顶点,再通过 2->3 这条边,到达 3 号顶点的路程。
我们发现 dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此 dis[3]要更新为 10。这个过程有个专业术语叫做 “松弛” 。即 1 号顶点到 3 号顶点的路程即 dis[3],通过 2->3 这条边 松弛成功。 这便是 Dijkstra 算法的主要思想: 通过 “边” 来松弛 1 号顶点到其余各个顶点的路程。
同理通过 2->4(e[2][4]),可以将 dis[4]的值从 ∞ 松弛为 4(dis[4]初始为 ∞,dis[2]+e[2][4]=1+3=4,dis[4]>dis[2]+e[2][4],因此 dis[4]要更新为 4)。
刚才我们对 2 号顶点所有的出边进行了松弛。松弛完毕之后 dis 数组为:
接下来,继续在剩下的 3、4、5 和 6 号顶点中,选出离 1 号顶点最近的顶点。通过上面更新过 dis 数组,当前离 1 号顶点最近是 4 号顶点。此时,dis[4]的值已经从“估计值”变为了“确定值”。下面继续对 4 号顶点的所有出边(4->3,4->5 和 4->6)用刚才的方法进行松弛。松弛完毕之后 dis 数组为:
继续在剩下的 3、5 和 6 号顶点中,选出离 1 号顶点最近的顶点,这次选择 3 号顶点。此时,dis[3]的值已经从“估计值”变为了“确定值”。对 3 号顶点的所有出边(3->5)进行松弛。松弛完毕之后 dis 数组为:
继续在剩下的 5 和 6 号顶点中,选出离 1 号顶点最近的顶点,这次选择 5 号顶点。此时,dis[5]的值已经从“估计值”变为了“确定值”。对5号顶点的所有出边(5->4)进行松弛。松弛完毕之后 dis 数组为:
最后对 6 号顶点的所有出边进行松弛。因为这个例子中 6 号顶点没有出边,因此不用处理。 到此,dis 数组中所有的值都已经从“估计值”变为了“确定值”。
最终 dis 数组如下,这便是 1 号顶点到其余各个顶点的最短路径。
OK,现在来总结一下刚才的算法。 Dijkstra算法的基本思想是:每次找到离源点(上面例子的源点就是 1 号顶点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
基本步骤如下:
在 博客 中看到两个比较有趣的问题,也是在学习Dijkstra时,可能会有疑问的问题。
当我们看到上面这个图的时候,凭借多年对平面几何的学习,会发现在“三角形ABC”中,满足不了 构成三角形的条件(任意两边之和大于第三边)。 纳尼,那为什么图中能那样子画?
还是“三角形ABC”,以A为起点,B为终点,如果按照平面几何的知识, “两点之间线段最短”, 那么,A到B的最短距离就应该是6(线段AB),但是,实际上A到B的最短距离却是3+2=5。这又怎么解释?
其实,之所以会有上面的疑问,是因为 对边的权值和边的长度这两个概念的混淆, 。之所以这样画,也只是为了方便理解(每个人写草稿的方式不同,你完全可以用别的方式表示,只要便于你理解即可)。
PS:数组实现邻接表可能较难理解,可以看一下 这里
参考资料:
Dijkstra算法是一种基于贪心策略的算法。每次新扩展一个路程最短的点,更新与其相邻的点的路程。当所有边权都为正时,由于不会存在一个路程更短的没扩展过的点,所以这个点的路程永远不会再被改变,因而保证了算法的正确性。
根据这个原理, 用Dijkstra算法求最短路径的图不能有负权边, 因为扩展到负权边的时候会产生更短的路径,有可能破坏了已经更新的点路径不会发生改变的性质。
那么,有没有可以求带负权边的指定顶点到其余各个顶点的最短路径算法(即“单源最短路径”问题)呢?答案是有的, Bellman-Ford算法 就是一种。(我们已经知道了 Floyd-Warshall 可以解决“多源最短路”问题,也要求图的边权均为正)
通过 邻接矩阵 的Dijkstra时间复杂度是 。其中每次找到离 1 号顶点最近的顶点的时间复杂度是 O(N),这里我们可以用 优先队列(堆) 来优化,使得这一部分的时间复杂度降低到 。这个我们将在后面讨论。
G. 简谈迪克斯特拉算法
一直想要学点简单的算法,叨叨了好久,开始吧【这篇文章的前言无非就是我想说点废话,大家可以选择性的过滤哈。】
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家 狄克斯特拉 于1959 年提出的,因此又叫 狄克斯特拉算法 。是从一个顶点到其余各顶点的 最短路径 算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
敲黑板~进入正题
迪杰斯特拉算法是目前 OIER 们最爱用的最短路算法,下面讲一下这个算法的思路【图丑,请大家忍耐一下】:
第一步,我们先把a加入集合,数组变成(s = {a}, dis[] = {0, ∞,∞,∞,∞,∞,∞,∞})
第二步,找到和a最近的点,为b,把b加入集合,并确定他的最短路径【要注意箭头方向哈仿歼塌】,数组变成(s = {a, b}, dis[] ={0,2,∞,∞,∞,∞,∞,∞})
第三步,找到和b最近的点,为d,把d加入集合,并确定他的最短路径【要注意箭头方向】,数组变成(s = {a, b, d}, dis[] = {0,2,∞,3,∞,∞,∞,∞})
第四步,找到和d最近的点,为e,把e加入集合,并确定他的最短路径【要注意箭头方向】,数组变成(s = {a, b, d, e}, dis[] = {0,2,∞,3,5,∞,∞,∞改纤})
第五步,找到和e最近的点,为f,把f加入集合,并确定他的最短路径【要注意箭头方向】,数组变成(s = {a, b, d, e, f}, dis[] = {0,2,∞,3,5,9,∞,∞})
第六步,找到和f最近的点,为g,把g加入集合,并确定他的最短路径【要注意箭头方向】,数组变成(s = {a, b, d, e, f, g}, dis[] = {0,2,∞,3,5,9,12,∞})
第七步,目前只剩下c和h了,那么我们先要找到距离集合路径最短的c,把c加备圆入集合,并确定他的最短路径,数组变成(s = {a, b, c, d, e, f, g}, dis[]= {0,2,13,3,5,9,12,∞})
第八步,最后一步,我们找到距离集合路径最短的h,把h加入集合,并确定他的最短路径,数组变成(s = {a, b, c, d, e, f, g, h}, dis[] = {0,2,13,3,5,9,12,18})
得嘞,这个大致的思路是这样的,还有后续哟,欲知后事如何,请看下回讲解~