最近花了大约一个月左右的时间集中刷了一些图论的题目。虽然收获了许多但是还是付出了很多作业没有做的代价0.0。在这里先把自己所做的关于最短路的基础算法来做一个总结,至少把学到的东西记录下来。

先说明一下在这里我们暂且认为n为图中顶点个数,m为图中边的个数,INF为极大值(可以是题目计算过程中不会的得到的一个大数字)。

然后说一下最近学到了什么吧。最初学习的是dij--O(n^2)的算法。这个在以前数据结构的时候就已经学习过了现在加强了一下,熟练掌握了算法思想然后会裸敲。接着当然就是dij--O(mlogn)的算法。了解算法思想并且明白了是如何优化的,然后会裸敲。接着是floyd算法这个最简单了只有几行代码。但是它题目中往往会结合一些动态规划来考察你,复杂度为O(n^3),还有就是关于bellmam-ford与SPFA是关于带负权边图上的最短路。前者的复杂度为O(nm)后者为O(kn)k为常数一般情况下小于2。

一、Dijkstra算法

首先讲一下关于dij算法的思想。首先dij算法使用的前提是图中不存在负权边

我们分三步来讲述dij:

(1)参数与返回值

dij算法是单元最短路所以我们需要告诉dij函数你的源点(s)是哪一个结点,然后函数执行完后dis数组中存放的就是s到图中所有结点的最短距离,如果不连通的话会返回极大值。

(2)初始化

在初始化过程中我们要定义vis数组--用来记录已经访问过的结点,并且清零。然后给dis数组赋初值INF(s点为0),表示初始情况下源点到除自己之外的所有结点都为无穷大。

(3)算法主体

我们执行n次循环,每次从dis数组中选出一个值最小的结点-标记此结点-对这个结点所连接的每一条边进行松弛

if(mindis+Map[min][j]<dis[j] && Map[min][j]!=INF && vis[j]==0)
  dis[j] = mindis+Map[min][j];

然后我们就可以给出算法的所有代码:

 1 /****************************************
 2      Dijkstra O(n^2) 单元最短路算法
 3      邻接矩阵
 4      By 小豪
 5 *****************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <string.h>
 9 #define INF 0x3f3f3f3f
10 #define LEN 1010
11 using namespace std;
12
13 int Map[LEN][LEN], dis[LEN], n, m;
14
15 void Dijkstra(int s)
16 {
17     int vis[LEN] = {0};
18     for(int i=1; i<=n; i++)
19         dis[i] = INF;
20     dis[s] = 0;
21     for(int i=0; i<n ;i++)
22     {
23         int min, mindis = INF;
24         for(int j=1; j<=n; j++)
25             if(dis[j]<mindis && vis[j] == 0)
26             {
27                 mindis = dis[j];
28                 min = j;
29             }
30         vis[min] = 1;
31         for(int j=1; j<=n; j++)
32             if(mindis+Map[min][j]<dis[j] && Map[min][j]!=INF && vis[j]==0)
33                 dis[j] = mindis+Map[min][j];
34     }
35 }
36
37
38 int main()
39 {
40 //    freopen("in.txt", "r", stdin);
41     return 0;
42 }

其实上述dij在实际竞赛中时不常用的,因为他的复杂度太高,不能符合比赛中大多数题目对于时间效率的要求。我们实际使用的dij使用优先队列优化的Dij时间复杂度为O(mlogn)。仔细想想我们会发现在最坏情况下也就是对于一个完全图m=n(n-1)那么这个版本的的dij复杂度不是退化成O(n^2logn)不仅较以前没有降低反而上升了,这还叫优化?等等,这只是理论上分析而已。在实际使用中由于他的入队条件往往得不到满足,所以实际的使用效率会大大的好于O(n^2)的版本,所以你大可放心使用。

下面来讲述一下到底是怎么对原来的算法进行优化的?前面我分了三块讲述的dij我们可以很清晰的看见影响复杂度的是第三块,第三块又分为两部分--

(1)找出dis最小的点

对于这一块直接使用优先队列就可以了,对于每次选出最小值的操作只需要logn的时间复杂度。

(2)对其连接的所有边进行松弛

对于这一块也很容易我们可以不用邻接矩阵而用邻接表存储。这样很容易证明当左右顶点都访问过后正好每一条边都被松弛了一次。

综上所述复杂度O(mlogn)由此产生。

下面我们给出代码:

 1 /****************************************
 2      Dijkstra O(mlogn) 单元最短路算法
 3      By 小豪
 4 *****************************************/
 5 #include <iostream>
 6 #include <cstdio>
 7 #include <cstring>
 8 #include <cstdlib>
 9 #include <algorithm>
10 #include <utility>
11 #include <vector>
12 #include <queue>
13 #include <stack>
14 #define INF 500001
15 #define LEN 50100
16 using namespace std;
17
18 typedef pair<int, int> pii;
19 vector<pii> Map[LEN];
20 int dis[LEN];
21
22 void init(){for(int i=0; i<LEN; i++)Map[i].clear();}
23
24 void Dijkstra(int vex){
25     priority_queue<pii, vector<pii>, greater<pii> > q;
26     int vis[LEN] = {0};
27     for(int i=0; i<LEN; i++) dis[i] = (i==vex?0:INF);
28     q.push(make_pair(dis[vex], vex));
29     while(!q.empty()){
30         pii nv = q.top(); q.pop();
31         int x = nv.second;
32         if(vis[x]) continue;
33         vis[x] = 1;
34         for(vector<pii>::iterator it = Map[x].begin(); it!=Map[x].end(); ++it){
35             int y = it->first, v = it->second;
36             if(dis[y]>dis[x]+v){
37                 dis[y] = dis[x]+v;
38                 q.push(make_pair(dis[y], y));
39             }
40         }
41     }
42 }
43
44 int main()
45 {
46 //    freopen("in.txt", "r", stdin);
47     return 0;
48 }

关于代码的说明:

这里我习惯用vector<pair<int,int> >来存储图。pair的第一个值表示指向的结点,第二个值表示边的权值。

初始化操作和参数返回值和原来没有区别,只是后来改成类似于BFS的形式每次取出一个节点,如果该节点已经被访问过则丢弃,否则松弛所有该结点连接的边,再把松弛好的dis,与结点入队(新更新的值可以用来更新其他的结点)。直到队列为空算法结束。

二、Floyd算法

对于floyd算法比较简单,也比较实用,它的特点就是代码特别短。在比赛的时候背出来就可以了。

这里先给出我的代码:

 1 /****************************************
 2      Floyd O(n^3) 最短路算法
 3      By 小豪
 4 *****************************************/
 5 #include <iostream>
 6 #include <cstdio>
 7 #include <cstring>
 8 #include <cstdlib>
 9 #include <cmath>
10 #include <algorithm>
11 #define LEN 1010
12 #define INF 500001
13 using namespace std;
14
15 int Map[LEN][LEN], dis[LEN][LEN];
16 int n, m;
17
18 void init()
19 {
20     for(int i=0; i<LEN; i++){
21         for(int j=0; j<LEN; j++){
22             Map[i][j] = INF;
23             if(i==j)Map[i][j] = 0;
24         }
25     }
26 }
27
28 void floyd()
29 {
30     for(int i=1; i<=n; i++){
31         for(int j=1; j<=n; j++){
32             dis[i][j] = Map[i][j];
33         }
34     }
35     for(int k=1; k<=n; k++){
36         for(int i=1; i<=n; i++){
37             for(int j=1; j<=n; j++){
38                 dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
39             }
40         }
41     }
42 }
43
44 int main()
45 {
46 //    freopen("in.txt", "r", stdin);
47     return 0;
48 }

算法的主体部分是三层for循环k表示i-j经过前k个结点所获得的最短路径,每一次比较原来的dis[i][j]是不是比经过k结点也就是dis[i][k]+dis[k][j]大,若果是则更新。

floyd算法的主题思想是动态规划。在实际运用中我们常常可以改变dis[i][j]状态的含义来计算出题目所需要的东西。这一类floyd变形的题目还是很常见,当然在状态记录信息不足时我们还可以增加一维用于记录其他信息(这是解动态规划题常用的方法),这里我就不再详细叙述了。

三、bellman-ford与SPFA算法(带负权的最短路问题)

在图论问题中我们还会遇到带负权图的单源最短路问题,这是dij算法就没有用武之地了,然而floyd算法的复杂度有过高(也有一些大材小用)。这是我们就需要用到下面两个算法:

1.bellman-ford算法

bellman-ford算法的不仅思想很简单,写起来也很简单,就两重循环对所有边松弛n次:

if(dis[y]>dis[x]+w[j])dis[y] = dis[x]+w[j];

这样在没有负权环的情况下我们可以求出图中的最短路。说明:算法中我们只存图的边即可。

代码如下:(部分代码)

1 for(int i=0; i<n-1; i++){
2     for(int j=0; j<m; j++){
3         int x = u[j], y = v[j];
4         if(dis[y]>dis[x]+w[j])dis[y] = dis[x]+w[j];
5     }
6 }    

虽然写起来简单,但是算法复杂度实在太高了。而实际使用中我们推荐使用更加优秀的SPFA算法。

2.SPFA算法

SPFA算法是西南交通大学段凡丁于1994年发表的。算法也十分容易实现,而且效率很不错。所以有很大的实用性,SPFA算法的思想是从广度优先搜索演变而来的。我们知道在对于一个不带权图上求最短路的时候我们常常会用用到BFS算法借助于队列先进先出的性质,先到达的结点所经历的步数一定是最短路。由于每个结点只入队一次,复杂度为O(n)。那么借助于这个思想是不是可以对于带权图也求出最短路呢。

SPFA就完成了这一点对于每次出队的节点对于所有这个结点连接的边执行松弛操作,若是dis数组被更新了则将结点重新入队。(因为更新好的结点有可能会影响到其他结点的最短路)这里就可以看出来每个结点可能多次入队,这里用k表示平均入队次数,所以复杂度为O(kn)在实际使用中k在2左右。可见spfa性能很卓越。

下面给出我的代码:

 1 /****************************************
 2      SPFA O(kn) 单源最短路算法
 3      By 小豪
 4 *****************************************/
 5 #include <iostream>
 6 #include <cstdio>
 7 #include <cstring>
 8 #include <cstdlib>
 9 #include <algorithm>
10 #include <queue>
11 #include <stack>
12 #include <vector>
13 #define LEN 1010
14 #define INF 0x3f3f3f3f
15 #define pb(a) push_back(a)
16 #define mp(a, b) make_pair(a, b)
17
18 using namespace std;
19
20 typedef pair<int, int> pii;
21 int n, dis[LEN];
22 vector<pii> Map[LEN];
23
24 //返回false有负环true最短路在dis数组中
25 bool SPFA(int s)
26 {
27     queue<int> q;
28     int vis[LEN] = {0}, cnt[LEN] = {0};
29     for(int i=0 ;i<n; i++)dis[i] = INF;
30     dis[s] = 0;
31     q.push(s);
32     vis[s] = 1;
33     cnt[s]++;
34     while(!q.empty()){
35         int nv = q.front(); q.pop();
36         for(int i=0; i<Map[nv].size(); i++){
37             int x = Map[nv][i].first, y = Map[nv][i].second;
38             if(dis[x] > dis[nv]+y){
39                 dis[x] = dis[nv] + y;
40                 if(!vis[x]){
41                     q.push(x);
42                     vis[x] = 1;
43                     cnt[x] ++;
44                     if(cnt[x]>n) return false;
45                 }
46             }
47         }
48         vis[nv] = 0;
49     }
50     return true;
51 }
52
53 int main()
54 {
55 //    freopen("in.txt", "r", stdin);
56     return 0;
57 }

对于代码的说明:

代码使用的存储结构与前面dij使用的邻接表是一样的,这里就不再讲一遍了。我们会发现这里多出来一个cnt数组。这个数组是干什么用的呢?

前面说过SPFA算法计算最短路图中是不能有负环的,那么如何判断负环,这里cnt数组就产生了作用,cnt是用来记录结点入队次数,可以证明当结点入队超过n次时说明图中存在负环。也有一些题目会要求你判断图中存不存在负环,这时候就可以使用SPFA算法。另一点和BFS区别就是在对于一个节点操作完后,我们需要去除结点标记。因为SPFA不像BFS每个结点只入队一次,而是需要多次入队,所以vis数组是用来标记当前节点是否存在队列中。

转载于:https://www.cnblogs.com/shu-xiaohao/p/3496038.html

最短路算法总结(入门版)相关推荐

  1. 《算法竞赛入门经典(第二版)》pdf

    下载地址:网盘下载 内容简介  · · · · · · <算法竞赛入门经典(第2版)>是一本算法竞赛的入门与提高教材,把C/C++语言.算法和解题有机地结合在一起,淡化理论,注重学习方法和 ...

  2. 算法竞赛入门经典(第二版)-刘汝佳-第六章 数据结构基础 习题(12/14)

    文章目录 说明 习题 习6-1 UVA 673 平衡的括号 习6-2 UVA 712 S - 树 习6-3 UVA 536 二叉树重建 习6-4 UVA 439 骑士的移动 习6-5 UVA 1600 ...

  3. 《算法竞赛入门经典(第2版)》

    <算法竞赛入门经典(第2版)> 基本信息 作者: 刘汝佳 丛书名: 算法艺术与信息学竞赛 出版社:清华大学出版社 ISBN:9787302356288 上架时间:2014-6-5 出版日期 ...

  4. 算法竞赛入门经典(第二版)第三章习题

    声明:作者水平有限,只是会基础C语言的小菜,C++还未入门.作者仅根据算法竞赛入门经典(第二版)书上第三章习题所述题意而编写,并未严格按照原题的输入输出编写,代码仅经过个人测试(OJ网站太慢了).代码 ...

  5. 《算法竞赛入门经典》(第二版)代码及详细解释(持续更新!)

    笔者中山大学硕士,医学生+计科学生的集合体,机器学习爱好者. 现发布[刘汝佳<算法竞赛入门经典>(第二版)--紫书]的例题和习题的代码和详细解释. 欢迎批评指正! 另外欢迎关注本人微信公众 ...

  6. 刘汝佳《算法竞赛入门经典(第二版)》习题(三)

    刘汝佳<算法竞赛入门经典(第二版)>第三章习题(一) 习题3-1 得分(ACM/ICPC Seoul 2005,UVa1585) 给出一个由O和X组成的串(长度为1~80),统计得分.每个 ...

  7. 刘汝佳《算法竞赛入门经典(第二版)》习题(六)

    刘汝佳<算法竞赛入门经典(第二版)>第四章习题(4-1~4-3) 习题4-1 象棋(Xiangai,ACM/ICPC Fuzhou 2011,UVa1589) 考虑一个象棋残局,其中红方有 ...

  8. 刘汝佳《算法竞赛入门经典(第二版)》习题(二)

    刘汝佳<算法竞赛入门经典(第二版)>第二章习题 目录 刘汝佳<算法竞赛入门经典(第二版)>第二章习题 习题2-1 水仙花数 习题2-2 韩信点兵 习题2-3 倒三角形 习题2- ...

  9. 补学图论算法:算法竞赛入门经典(第二版)第十一章:

    补学图论算法:算法竞赛入门经典(第二版)第十一章: 倒排索引还没有实现! 下面是左神的图论算法,并查集笔记.和一个美团题目. ''' https://www.nowcoder.com/live/11? ...

最新文章

  1. 通俗篇:一文搞定矩阵相关概念及意义
  2. 屡现黑马!2021THE泰晤士高等教育学科排名发布!斯坦福成为最大赢家,清华、北大强势逆袭!...
  3. jffs2 告警 和 一般性错误
  4. 【cocos2d-x 手游研发----研发思路及感想】
  5. 可视化COCO分割标注文件,以及单个json合成coco格式标注文件
  6. Android中怎获取json,Android应用中如何解析获取的json数据
  7. android 图片转字符串,图片转字符文字怎么转?安卓字符图App
  8. Redis学习笔记001---Windows下安装Redis
  9. AsnycTask的内部的实现机制
  10. python大漠插件官网视频教程_python使用大漠插件进行脚本开发的尝试(一)
  11. 基于ZigBee cc2530单片机多传感器的智能阳台仿真设计与实现
  12. layui 加载loding图标
  13. Vector诊断系统开发流程及其工具链
  14. OSI七层——物理层介绍和安全
  15. 听着熟悉的《东风破》,好想你~
  16. FlinkSQL to Kafka连接器报错:could not find any factory for identifier ‘kafka‘ that implements
  17. 有一种心酸,叫靠自己
  18. 信奥赛一本通 C++题解 2036【例5.3】开关门
  19. 学生学籍管理-学生信息管理-项目实战
  20. 增值税发票管理解决方案

热门文章

  1. Java程序中的死锁
  2. lol简介/html
  3. hdu 1688 Sightseeing
  4. 《LeetCode力扣练习》第96题 不同的二叉搜索树 Java
  5. 数据结构1:单链表反转java代码解释
  6. js 数组 实现 完全树_JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧
  7. sql 将多个括号及内容删除_新浪微博将对逝者账号设置保护:不能登录、新发和删除内容...
  8. as本地仓库更改_Android Studio 之 Gradle与Project Structure详解
  9. 高考成绩2021年怎么查询,2021高考成绩怎么查询 2021年各省市高考成绩查询时间介绍...
  10. python小白逆袭大佬_飞桨深造学院百度深造7天打卡第6期:python小白反攻大神的经验,深度,学习,日,第六期,Python,小白逆袭,结营,心得...