一步一步深入理解Dijkstra算法
先简单介绍一下最短路径:
最短路径是啥?就是一个带边值的图中从某一个顶点到另外一个顶点的最短路径。
官方定义:对于内网图而言,最短路径是指两顶点之间经过的边上权值之和最小的路径。
并且我们称路径上的第一个顶点为源点,最后一个顶点为终点。
由于非内网图没有边上的权值,所谓的最短路径其实是指两顶点之间经过的边数最少的路径。
我们时常会面临着对路径选择的决策问题,例如在中国的一些一线城市如北京、上海、广州、深圳等,一般从A点到到达B点都要通过几次地铁、公交的换乘才可以到达。
有些朋友想用最短对的时间,有些朋友想花最少的金钱,这就涉及到不同的方案,那么如何才能最快的计算出最佳的方案呢?
在网图和非网图中,最短路径的含义是不同的。
- 网图是两顶点经过的边上权值之和最少的路径。
- 非网图是两顶点之间经过的边数最少的路径。
我们把路径起始的第一个顶点称为源点,最后一个顶点称为终点。
关于最短路径的算法,我们会介绍以下算法:
- 迪杰斯特拉算法(Dijkstra)
求V0到V8的最短路径
你找到了吗
好了,我想你大概明白了,这个迪杰斯特拉算法是如何工作的。
它并不是一下子就求出了V0到V8的最短路径,而是一步步求出它们之间顶点的最短路径,过程中都是基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到你要的结果。
迪杰斯特拉(Dijkstra)算法
1. 迪杰斯特拉(Dijkstra)算法简介
迪杰斯特拉(dijkstra)算法是典型的用来解决最短路径的算法,也是很多教程中的范例,由荷兰计算机科学家狄克斯特拉于1959年提出,用来求得从起始点到其他所有点最短路径。该算法采用了贪心的思想,每次都查找与该点距离最近的点,也因为这样,它不能用来解决存在负权边的图。解决的问题大多是这样的:有一个无向图G(V,E),边E[i]的权值为W[i],找出V[0]到V[i]的最短路径。
2.迪杰斯特拉算法的原理
(附上小图一张)
①首先,引入一个辅助向量D,它的每个分量D[i]表示当前所找到的 Dijkstra算法运行动画过程 Dijkstra算法运行动画过程 从起始点 (即源点 )到其它每个顶点 的长度。例如,D[3] = 2表示从起始点到顶点3的路径相对最小长度为2。这里强调相对就是说在算法执行过程中D的值是在不断逼近最终结果但在过程中不一定就等于长度。
②D的初始状态为:若从v 到v[i]有弧(即从v到v[i]存在连接边),则D[i]为弧上的权值(即为从v到v[i]的边的权值);否则置D[i]为∞。显然,长度为 D[j]= Min{ D |v[i]∈V } 的路径就是从v出发到顶点v[j]的长度最短的一条路径,此路径为(v,v[j])。
③那么,下一条长度次短的是哪一条呢?也就是找到从源点v到下一个顶点的最短路径长度所对应的顶点,且这条最短路径长度仅次于从源点v到顶点v[j]的最短路径长度。 假设该次短路径的终点是v[k],则可想而知,这条路径要么是(v,v[k]),或者是(v,v[j],v[k])。它的长度或者是从v到v[k]的弧上的权值,或者是D[j]加上从v[j]到v[k]的弧上的权值。
④一般情况下,假设S为已求得的从源点v出发的最短路径长度的顶点的集合,则可证明:下一条次最短路径(设其终点为x)要么是弧(v,x),或者是从源点v出发的中间只经过S中的顶点而最后到达顶点 的路径。 因此,下一条长度次短的的最短路径长度必是D[j]= Min{ D[i] |v[i]∈V-S },其中D 要么是弧( v,v[i])上的权值,或者是D[i]( v[k]∈S)和弧(v[k] ,v[i] )上的权值之和。
3.迪杰斯特拉算法的实现过程
①先取一点v[0]作为起始点,初始化dis[i],d[i]的值为v[0]到其余点v[i]的距离w[0][i],如果直接相邻初始化为权值,否则初始化为无限大;
②将v[0]标记,vis[0] = 1(vis一开始初始化为0);
③找寻与v[0]相邻的最近点v[k],将v[k]点记录下来,v[k]与v[0]的距离记为min;
④把v[k]标记,vis[k]=1;
⑤查询并比较,让dis[j]与min+w[k][j]进行比较,判断是直接v[0]连接v[j]短,还是经过v[k]连接v[j]更短,即dis[j]=MIN(dis[j],min+w[k][j]);
⑥继续重复步骤③与步骤⑤,知道找出所有点为止。
4.迪杰斯特拉的实现代码(C/C++)
1 int Dijkstra(int n) 2 { 3 //初始化v[0]到v[i]的距离 4 for(int i=1;i<=n;i++) 5 dis[i]=w[0][i]; 6 vis[0]=1;//标记v[0]点 7 for(int i=1;i<=n;i++) 8 { 9 //查找最近点 10 int minn=INF,k=0; 11 for(int j=0;j<=n;j++) 12 if(!vis[w]&&dis[j]<minn) 13 minn=dis[w],k=j; 14 vis[k] = 1;//标记查找到的最近点 15 //判断是直接v[0]连接v[j]短,还是经过v[k]连接v[j]更短 16 for(int j = 1; j <= n; j++) 17 if(!vis[j]&&minn+w[k][j]<dis[j]) 18 d[j]=minn+w[k][j]; 19 } 20 return dis[j]; 21 }
贴上一道Dijkstra裸题:HDU 2544
下面是DijkstraAC的代码:
1 #include<stdio.h> 2 #include<string.h> 3 #define inf 0xfffffff 4 int mp[110][110],dis[110],visit[110]; 5 int n,m; 6 int Dijstra() 7 { 8 int i,j,pos=1,minn,sum=0; 9 memset(visit,0,sizeof(visit)); 10 for(i=1;i<=n;++i) 11 { 12 dis[i]=mp[1][i]; 13 } 14 visit[1]=1; 15 dis[1]=0; 16 for(i=1;i<n;i++) 17 { 18 minn=inf; 19 for(j=1;j<=n;++j) 20 { 21 if(!visit[j]&&minn>dis[j]) 22 { 23 minn=dis[j]; 24 pos=j; 25 } 26 } 27 sum+=minn; 28 visit[pos]=1; 29 for(j=1;j<=n;++j) 30 { 31 if(!visit[j]&&dis[j]>dis[pos]+mp[pos][j]) 32 dis[j]=dis[pos]+mp[pos][j]; 33 } 34 } 35 return dis[n]; 36 } 37 int main() 38 { 39 int i,j; 40 while(~scanf("%d%d",&n,&m),n||m) 41 { 42 for(i=1;i<=n;++i) 43 { 44 for(j=1;j<=n;++j) 45 { 46 mp[i][j]=inf; 47 } 48 } 49 int a,b,c; 50 for(i=1;i<=m;++i) 51 { 52 scanf("%d%d%d",&a,&b,&c); 53 if(c<mp[a][b]) 54 mp[a][b]=mp[b][a]=c; 55 } 56 int count=Dijstra(); 57 printf("%d\n",count); 58 } 59 return 0; 60 }
当时我是用Floyd过的,有兴趣的点击这里
下面是一些题目的锦集,有兴趣的同学可以做做看哦
1.poj1062 昂贵的聘礼(中等)
此题是个经典题目;用Dijkstra即可;但是其中的等级处理需要一定的技巧;
要理解好那个等级制度;这个处理好,基本就是裸体Dijkstra;
2 poj1125 Stockbroker Grapevine(基本)
这个是简单Floyd,需要求出的是每对顶点之间的最短路径;
然后找到那个所需时间最小的那个人中的所需时间;
3,poj 1502 MPI Maelstrom(基本)【已经解决之,Dijkstra直接水之】
这题是邻接矩阵的Dijkstra就可以解决的;
直接水之;
4,poj 1511 Invitation Cards(中等)
这个时间上有点卡了。Dijkstra,bellman可能会TLE;用SPFA+邻接表可以过的;
有个地方注意一下就好了,每个志愿者回来的时候的最短路径;将原图的每条边反向一下,对端点1再来
SPFA就可以来;
正向图的结果+逆向图的结果就是所求;
5,poj 1724 ROADS(中等偏上)
题意是在一定的coins的限制下的最短路径;可以用Dijkstra的变形;
用邻接边来存储边;
松弛过程中用优先队列(边的长度短的优先)来存储边,将符合条件(coins限制)的边都加入优先队列;
直到找到延伸到最后一个顶点即可终止循环; 因为最先到达的一定是最短路径,在coins的限制条件下;
6,poj1797 Heavy Transportation(中等)
从端点1到端点n的能够通过的最大载重;
可以用Dijkstra变形一下,在松弛时要改变一下松弛的条件;
另外results数组中存储的不是每个点到1的最短距离,而是能够通过的最大载重;
这题的输出让我灰常无语,以后输出要看清啦。。
7,poj 1860 currency exchange(基本)
这个是bellman_ford的经典应用;
一个套汇问题,就是通过一系列的货币交换能够到达价值增加的目的;
就是类似判断有没有负权回路;
类似与poj2240,poj3259;
8,poj2240 Arbitrage(基本)http://hi.baidu.com/lewutian
这个是poj1860的简化版本;就不多说了。。
直接bellman水之;
9,poj 2253 Frogger(中等)
和poj1797类似,所求的正好相反,也是Dijkstra的变形经典应用;
改变一下松弛时的条件;
10,poj 2387 Till the Cows come home(基本)
注意其中可能有重边;然后就是赤裸的Dijkstra;
11,poj 2502 Subway(基本)
可以用Floyd来搞定,关键是哪个边的存储,存储后就是灰常简单的Floyd了;
12,poj 3013 Big Christmas Tree(中等)
这个要有个重要的转化;首先price of an edge will be (sum of weights of all descendant nodes) × (unit price of the edge).
这句指出每条边的代价为该边所有子孙节点权值之和乘以该边的权值。
换个角度就是每个节点的代价为该节点到根节点的所有边的权值乘以该节点的权值。
其实就是求从端点1到每个点的最短路径*改点的权值,,然后之和;
貌似,数据有点大,用SPFA吧。。
13,poj 3037 Skiing(中等)
这个题有点意思;刚开始想用bfs;
后来发现对于每个点从该点出发的速度是恒定的,例如从a->b->c;则c出发的速度就是V*2^(A-B)*2^(B-C)=V*2^(A-C);
所以直接求最短路径就可以了,边也知道了。用spfa。。
14,poj 3159 Candies(中等)
我靠,这个题时间卡的好紧啊!我的spfa是1400ms,时限是1500ms,汗一下;
题意有点难理解,想明白了,其实就是求一个从1->n的最短路径;
15,poj 3259 Wormholes(简单)
这个题和poj1860,poj3259基本一样;
求负权回路是否存在;用bellman直接水之;
16,poj 3268 Silver Cow Party(基本)
Dijkstra可以直接过的。。只不过求的有变化;
17,poj 3377Ferry Lanes(中等)
这题可以用最短路做的;但是我看和导论那个流水线那个dp例子灰常像;
于是就dp过了,其中有个地方需要注意,dp的话,就是可以需要检查两端的情况;
有兴趣的可以两种都试试;
18,poj 3615 Cow Hurdles(中等)
Floyd求出每个端点之间的路径中最大高度是最小的那个最大高度;
要改变一下松弛的条件;
19,poj 3660 Cow Contest(中等)
这个题有点topsort的意思,其实可以用Floyd来做,而且用的很巧妙;
邻接矩阵中用0,1,2来分别存储关系不能确定,在之前,在之后;
然后判断每个点哪行,如果除了对角线处,没有0出现的话,那么它的位置就可以确定了。。
Dijkstra所需的数据结构:
dist[]数组,用于存储每个点的前缀点,可以从终点回溯整个最短路径;
shortest[]数组,用于存储目前该点的最短距离;
ifVisited[]数组,用于将已经找到最短路径的点筛选出去。
回顾一下dijkstra的几个基本步骤:
1.初始化起点,初始化所有数组,起点的shortet设置为0;
2.开始遍历所有点V次,V为顶点数目-1;
3.每次遍历的开始,选出shortest的顶点,将ifVisited置为true;
4.遍历过程中,如果当前筛选出的点的dist+两点间距离<某个点的shorest,则更新该点的shortest;
最终,shortest记录的是原点到每个点的最短距离。
局限性:Dijkstra不能求出任意两个点之间的最短路径,只能求出某一点到其他任一点的最短路径,并且不支持负权边;
如果要支持负权边,则使用bellman-ford,如果要支持任意两点最短路径,需要使用flyod。
要技艺超群,要予人温暖,越努力才能越幸运,不让那些为我付出过的人失望
-----Angel_Kitty
转载请注明:http://www.cnblogs.com/ECJTUACM-873284962/
作 者:Angel_Kitty
出 处:https://www.cnblogs.com/ECJTUACM-873284962/
关于作者:阿里云ACE,目前主要研究方向是Web安全漏洞以及反序列化。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力!
欢迎大家关注我的微信公众号IT老实人(IThonest),如果您觉得文章对您有很大的帮助,您可以考虑赏博主一杯咖啡以资鼓励,您的肯定将是我最大的动力。thx.
我的公众号是IT老实人(IThonest),一个有故事的公众号,欢迎大家来这里讨论,共同进步,不断学习才能不断进步。扫下面的二维码或者收藏下面的二维码关注吧(长按下面的二维码图片、并选择识别图中的二维码),个人QQ和微信的二维码也已给出,扫描下面👇的二维码一起来讨论吧!!!
欢迎大家关注我的Github,一些文章的备份和平常做的一些项目会存放在这里。