非负权值有向图上的单源最短路径算法之Dijkstra算法
问题的提法是:给定一个没有负权值的有向图和其中一个点src作为源点(source),求从点src到其余个点的最短路径及路径长度。求解该问题的算法一般为Dijkstra算法。
假设图顶点个数为n,则针对其余n-1个点需要分别找出点src到这n-1个点的最短路径。Dijkstra算法的思想是贪心法,先找出最短的那条路径,其次找到次短的,再找到第三短的,依次类推,直到找完点src到达其余所有点的最短路径。下面举例说明算法和贪心过程。
如下图所示(该图源自《数据结构预(用面向对象方法与C++语言描述)(第2版)》殷人昆 主编清华大学出版社),求顶点0到其余个点的最短路径其路径长度。
说明一下,下面表述过程中有一个【直接到达】,例如 从点0直接到达点K意思是有一条边从0指向K。
从定点0出发能直接达到点1、3、4,不能直接到达的点,认为点0直接到达这些点的距离为无穷大,所以这些直接路径中,最短的路径为0到1,长度为10。可以认定,从0到点1的最短路径就是0→1,否则,假设存在另外一条从0到1的路径,经过其他点,例如点k,使得该路径0→k+k→3的路径长度<10,然而已知,从0直接到达除1之外的其他点k的距离 > 从0直接到达点1的距离,所以这条路径不可能是最短路径。这样,就找出了从0到1的最短路径。
然后,找出从0直接到达其他的点最短路径,最短为0→3,路径长度为30,那么能否像确定0→1一样确定0→3为从0到达3的最短路径呢?不能,因为有可能存在另外一条从0到3的路径,经过其他点,例如k,使得该路径0→k+k→3的路径长度<30,因为存在0→k的长度<30,所以有可能 路径(0→k)+(k→3)的路径长度<30,所以需要验证一下这种可能是否存在。
上面通过实例对Dijkstra算法的贪心过程进行了简单说明,下面给出算法过程。
Dijkstra算法过程:设图的邻接矩阵为G[n][n],顶点集合为V,G[i][j]表示从i到j的直线距离,求定点v到达其他定点的最短路径及路径长度。
Step1:令集合S={v},集合S用于存储已经找到从v到达该点的最短路径的顶点,dist[i]用于存储v到i的最短路径长度,初始化的时候令dist[i] = G[v][i](i取遍V-S中所有的点)。
Step2:寻找k使得dist[k]= min{dist[i]},令S=S+{k}(注意这里+表示取两个集合的并集)。
Step3:更新dist[i] = min { dist[i],dist[k]+G[k][i] } (i取遍V-S中所有的点)。
Step4:判断,若S==V,则停止算法,dist[i]即为v到i的最短路径长度;否则,转到Step2。
下面给出不严密证明:
v作为源点,算法开始的时候,没有找到任何点的最短路径,所以Step2寻找k使得dist[k]= min{dist[i]},此时k的最短路径已经找到,长度即为dist[k],因为从v直接到达到k的路径最短,若让v经过其他点再到达k,必然使得路径增长(这也是为什么要求图上的权值非负)。所以v到k路径找到,将k加入到集合S中。
进而Step3,看能否让从v到其他点i的路径经过k再到i,使得路径变短,若能则更改路径长度,否则不改变。这样到Step2继续找最短路径,寻找k使得dist[k]= min{dist[i]},对于找到的顶点k,最短路径也找到了的。如此循环下去,直到找到从v到所有点的最短路径。
Step4判断,是否从v到达所有点的最短路径都已经找到,即S==V。
问题1:在Step2和Step3中可能出现这样如图的问题,0为源点,首先选出最短路径的点一次为2和1,那么在仅剩下3,在运行Step2的时候,就需要考虑 dist[3] = min{dist[3],dist[1]+G[1][3]},dist[3]=无穷,达到3的最短路径必须通过1,这样最短路径为0->1->3,长度为13,比0->2->3的路径长度12要长,出现错误!错误在于,在这里进行Step2之前,在上一次循环中Step3的时候,已经对dist[3]进行更新,更新为dist[3]
= min{dist[3],dist[2]+G[2][3]},dist[3]=无穷大,dist[2]+G[2][3]=5+7=12,所以下一次循环的时候,dist[3]=12而非无穷,dist[3]=min{dist[3],dist[1]+G[1][3]}=12,
这样就不会出错了。关键是每一步都对没有找到的最短路径的点的路径长度进行了更新。
问题2:dist[i]记录了从v到达i的最短路径长度,如何记录从v到达每个点的具体的最短路径呢?
利用Dijkstra算法求出来的最短路径的特点:任取从v到达k的最短路径上的一个点i(非v非k),那么这条路径上从v到i的那部分路径就是从v到i的最短路径,因为Dijkstra是按照路径长度递增的顺序求最短路径的。这样,就可以在每次求出v到达一个点k的最短路径的时候,记录k之前的那个点,最后逆向即可找到具体路径。
下面通过Dijkstra算法求解前面的例子。
如图,求顶点0到其余个点的最短路径其路径长度。
初始化:S={0},dist[1]=10,dist[2]=INF,dist[3]=30,dist[4]=100,INF表示无穷大。另外增加一个数组path,记录找到最短路径的点之前的那个点,初始化的时候都初始化为path[i]=0(源点)。
Step2:min{dist[1]=10,dist[2]=INF,dist[3]=30,dist[4]=100}=dist[1],所以S={0,1}。
Step3:更新dist[]数组,dist[2]=min{dist[2],dist[1]+G[1][2]}=min{INF,10+50}=60,path[2]=1;
dist[3]=min{dist[3],dist[1]+G[1][3]}=min{30,10+INF}=30,path[3]不改变;
dist[4]=min{dist[4],dist[1]+G[1][4]}=min{100,10+INF}=100,path[4]不改变;
Step2:min{dist[2]=60,dist[3]=30,dist[4]=100}=dist[3],所以S={0,1,3}。
Step3:更新dist[]数组,dist[2]=min{dist[2],dist[3]+G[3][2]}=min{60,30+20}=50,path[2]=3;
dist[4]=min{dist[4],dist[3]+G[3][4]}=min{100,30+60}=90,path[4]=3;
Step2:min{dist[2]=50,dist[4]=90}=dist[2],所以S={0,1,3,2};
Step3:更新dist[]数组,dist[4]=min{dist[4],dist[2]+G[2][4]}=min{90,50+10}=60,path[4]=2;
Step2:只有dist[4]一个了,min{dist[4]}=dist[4],所以S={0,1,3,2,4}==V,停止算法。
对于1,path[1]=0,找到源点0,所以最短路径为0—>1,路径长度为dist[1]=10。
对于2,path[2]=3,path[3]=0,找到源点0,所以最短路径为0—>3—>2,路径长度为dist[2]=50。
对于3,path[3]=0,找到源点0,所以最短路径为0—>3,路径长度为dist[3]=30。
对于4,path[4]=2,path[2]=3,path[3]=0,所以最短路径为0—>3—>2—>4,路径长度为dist[4]=60。
用Dev-C++编写的C++代码:
#include<iostream>
using namespace std; int INF = 9999999; void Dijkstra(int *d[],int dist[],int path[],int n,int src)
{ //dist[i]记录从源点src到点i的路径长度,path[i]记录最短路径上点i前面的那个点
for(int i=0;i<n;i++) //dist[]和path[]的初始化
{
dist[i] = d[src][i];//dist[i]初始化为src到i之间的边的长度,没有从src指向i的边,那就是///无穷大
path[i] = src; //path[i]统一初始化为src,因为此时从src到i的路径就是从src到i的边
} bool *S = new bool[n]; //集合S,用于存储已经找到从src到达i的最短路径的点i
for(int i=0;i<n;i++) //初始化S
S[i] = false; //开始最短路径都没有找到,所以初始化false
S[src] = true; //点src已经找到(本来不用考虑src,但为后面代码简便,将src排除在 “未找到//最短路的点”之列) while(1) //看后面退出循环的条件
{
int temp = INF,k;
for(int i=0;i<n;i++) //对于所有的不在S中的i,找到 dist[i]的最小值
{
if(S[i]==false && dist[i]<temp)
{
k = i;
temp = dist[i];
}
} //最小值对应的点为k,最小值即为k的最短路径长度
S[k] = true; //将点k放入集合S
for(int i=0;i<n;i++) //更新每条路径的长度
{
if(S[i]==false && dist[k]+d[k][i]<dist[i]) //前面一个条件表明针对的是没有找到<span style="white-space:pre"> </span>//最短路径的点,后面条件表明路径经过点k则路径变短
{
dist[i] = dist[k] + d[k][i]; //更改路径长度
path[i] = k; //那么点i之前的那个点就是点k
}
} //下面检验是否退出循环---所有点是否都在S当中
bool ok = true;
for(int i=0;i<n;i++)
{
if(S[i]==false)
{
ok = false;
break;
}
}
if(ok==true)
break;
}
} int main()
{
const int n = 5;
int D[n][n] = {{0,10,INF,30,100},{INF,0,50,INF,INF},{INF,INF,0,INF,10},{INF,INF,20,0,60},{INF,INF,INF,INF,0}}; int **d = new int* [n];//不能直接传入D作为参数,需要进行一定的处理,将二维数组转化为一维数组,<span style="white-space:pre"> </span>//并且该数组类型为 int* 类型
for(int i=0;i<n;i++)
{
d[i] = new int[n];
for(int j=0;j<n;j++)
d[i][j] = D[i][j];
}
int path[n],dist[n];
int src = 0;
Dijkstra(d,dist,path,n,src);
for(int i=0;i<n;i++) //通过数组path[],寻找具体的最短路径,输出最短路径长度
{
if(i!=src)
{
int Road[n] = {i};
int k=1,temp = path[i];
while(1)
{
Road[k] = temp;
k ++;
if(temp == src) break;
else temp = path[temp];
}
cout << "从 " << src << " 到达 " << i << " 的最短路径长度为" << dist[i] << ",最短路劲为:";
for(int i=k-1;i>0;i--)
cout << Road[i] << "->";
cout << Road[0] << endl;
}
} return 0;
}
如有纰漏,请指正!
非负权值有向图上的单源最短路径算法之Dijkstra算法的更多相关文章
- 单源最短路径问题2 (Dijkstra算法)
用邻接矩阵 /* 单源最短路径问题2 (Dijkstra算法) 样例: 5 7 0 1 3 0 3 7 1 2 4 1 3 2 2 3 5 2 4 6 3 4 4 输出: [0, 3, 7, 5, 9 ...
- 图论(四)------非负权有向图的单源最短路径问题,Dijkstra算法
Dijkstra算法解决了有向图G=(V,E)上带权的单源最短路径问题,但要求所有边的权值非负. Dijkstra算法是贪婪算法的一个很好的例子.设置一顶点集合S,从源点s到集合中的顶点的最终最短路径 ...
- 单源最短路径问题之dijkstra算法
欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. 算法的原理 以源点开始,以源点相连的顶点作为向外延伸的顶点,在所有这些向外延伸的顶 ...
- 单源最短路径—Bellman-Ford和Dijkstra算法
Bellman-Ford算法:通过对边进行松弛操作来渐近地降低从源结点s到每个结点v的最短路径的估计值v.d,直到该估计值与实际的最短路径权重相同时为止.该算法主要是基于下面的定理: 设G=(V,E) ...
- 单源最短路径问题1 (Bellman-Ford算法)
/*单源最短路径问题1 (Bellman-Ford算法)样例: 5 7 0 1 3 0 3 7 1 2 4 1 3 2 2 3 5 2 4 6 3 4 4 输出: [0, 3, 7, 5, 9] */ ...
- 图->最短路径->单源最短路径(迪杰斯特拉算法Dijkstra)
文字描述 引言:如下图一个交通系统,从A城到B城,有些旅客可能关心途中中转次数最少的路线,有些旅客更关心的是节省交通费用,而对于司机,里程和速度则是更感兴趣的信息.上面这些问题,都可以转化为求图中,两 ...
- 单源最短路径-迪杰斯特拉算法(Dijkstra's algorithm)
Dijkstra's algorithm 迪杰斯特拉算法是目前已知的解决单源最短路径问题的最快算法. 单源(single source)最短路径,就是从一个源点出发,考察它到任意顶点所经过的边的权重之 ...
- 单源最短路径 Bellman_ford 和 dijkstra
首先两个算法都是常用于 求单源最短路径 关键部分就在于松弛操作 实际上就是dp的感觉 if (dist[e.to] > dist[v] + e.cost) { dist[e.to] = dist ...
- 单源最短路:Dijkstra算法 及 关于负权的讨论
描述: 对于图(有向无向都适用),求某一点到其他任一点的最短路径(不能有负权边). 操作: 1. 初始化: 一个节点大小的数组dist[n] 源点的距离初始化为0,与源点直接相连的初始化为其权重,其他 ...
随机推荐
- LTE中基于S1的切换
1:源eNodeB决定进行基于S1的切换.S1切换的原因可能是源eNodeB和目标eNodeB之间不存在X2连接,或者源eNodeB根据其他情况作出的判断. 2:源eNodeB向源MME发送Hando ...
- [转]网页ContentType详细列表
本文转自:来老师的专栏 http://blog.csdn.net/sweetsoft/article/details/6512050 不同的ContentType 会影响客户端所看到的效果.默认的 ...
- [剑指Offer] 26.二叉搜索树与双向链表
[思路]因为二叉搜索树的中序遍历就是递增排列的,所以只要在中序遍历时将每个结点放入vector中,再分别为每个结点的左右指针赋值即可. /* struct TreeNode { int val; st ...
- 建议 里面的sql查找单列 外面的sql查找所有列 这样方便查找数据
- 判断腾讯QQ是否在线
http://webpresence.qq.com/getonline?Type=1&1617052138: 判断腾讯QQ是否在线接口. 下面是个简单的例子: <!doctype htm ...
- TCP/IP Note1
TCP/IP(Transmission Control Protocol / Internet Protocol)是用于Internet的通信协议. 计算机通信协议:是指对那些计算机必须遵守以便彼此通 ...
- 十个迅速提升JQuery性能的技巧
本文提供即刻提升你的脚本性能的十个步骤.不用担心,这并不是什么高深的技巧.人人皆可运用!这些技巧包括: 使用最新版本 合并.最小化脚本 用for替代each 用ID替代class选择器 给选择器指定前 ...
- 编写一个 Chrome 浏览器扩展程序
浏览器扩展允许我们编写程序来实现对浏览器元素(书签.导航等)以及对网页元素的交互, 甚至从 web 服务器获取数据,以 Chrome 浏览器扩展为例,扩展文件包括: 一个manifest文件(主文件, ...
- Fragment里使用 PhoneGap 的 CordovaWebView
首先说明一下为什么要使用 CordovaWebView 而不直接使用 WebView 呢?由于 Android4.4 版本之后对 WebView 做了很大的改变,具体参考 这篇文章:理解WebKit和 ...
- org.apache.http.conn.HttpHostConnectException: Connection to xxx refused.
if you are using emulator to run your app for local server. mention the local ip as 10.0.2.2 and hav ...