文章目录

  • 一 前言
  • 二 Dijkstra 算法讲解
    • 1. 贪心算法的证明
    • 2. 算法实现说明
    • 3. 初版Dijkstra算法代码
  • 三 时间复杂度优化
    • 1. 优化策略
    • 2. 优化后的代码
  • 四 结语

一 前言

  上一次的文章更新还是在5月份,现在都快8月份了,整整三个月没有记录学习的点点滴滴。哎,主要是现在自己面临读研的一系列事情,到现在面试结果也没有出来,如果没中,那真的要给老师,给招生办跪下了,呜~~~~~~, 小菜鸡在大佬面前瑟瑟发抖,给我一个读书的机会吧!!!
   本次记录的是最短路径算法中的单源最短路经Dijkstra算法,也就是确定图中某一个点,求出图中其他所有点到该点的最短距离分别是多少

二 Dijkstra 算法讲解

1. 贪心算法的证明

Dijkstra算法的基本思想是贪心思想,关于贪心算法求解问题时,我们需要进行证明两点:

  1. 最优子结构
  2. 局部最优性(局部最优解可以得到全局最优解)
    关于算法的证明,小编就不多说了,这里直接分享一篇博客:迪杰斯特拉(Dijkstra)算法最通俗易懂的讲解 ,大家要是觉得解释的不是很好,最好还是去算法书上看看 。

2. 算法实现说明

在实现过程中,重要的就是我们引入了两个数组:dis数组和vis数组
dis数组:用来记录每个点到原点的最短路经。
vis数组:用来标记,某个点是否已经扩展过。
我们要做的就是在所有未扩展的点中,选出一个dis值最小的点进行松弛操作,这样n - 1次操作之后,即可知道原点到所有其他点的最短距离。

算法核心操作:
  现在假设我们已知结点A到源点S的最短距离为X,也存在从结点A指向结点B的有向边,该边的权值为val,显然结点B到原点会有一个距离,我们假设为Y,若X + val < Y,换句话说就是,我们现在找到了源点S到结点B的更短的一个路径,此时我们更新dis数组中到结点B到源点的距离值。

现在我们模拟一遍算法的执行流程

  1. 首先初始化dis数组和vis数组,dis数组中源点的值为0,剩余点的dis值为极大值;vis数组中均为0 ,其中0表示没有扩展,1表示该点已被扩展。
  2. 遍历dis数组,找到值最小,且没有扩展(vis值为0)的点,此时为1号结点。观察图可知,1号结点可以到达2, 3号结点,所以计算可知,结点2到源点的距离为6 + 0 = 6 < dis[2],结点3到源点的距离为0 + 1 = 1 < dis[3], 所以2, 3号结点均可以被更新。至此1号结点已经被扩展,标记vis[1] = 1;
  3. 遍历dis数组,找到值最小,且没有扩展(vis值为0)的点,此时为3号结点。观察图可知,3号结点可以到达2,4,6号结点,计算可知:2号结点到源点的距离为3 + dis[3] = 3 + 1 = 4(连接3号结点和2号结点的边的权值为3,3号结点到源点的最小距离为1)这个新的距离比原来2号结点到源点的距离6要小,所以我们需要进行距离的更新。同理,我们更新4和6号结点的距离。
  4. 按照之前的规则,我们相似操作,最终,我们可以得到如下的结果

3. 初版Dijkstra算法代码

我们以洛谷第3371题为例子:P3371 【模板】单源最短路径(弱化版)
解题代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;#define MAX_N 10000
#define MAX_M 500000
#define INF 0x3f3f3f3fstruct edge {int to, val, next;
} edg[MAX_M + 5];int head[MAX_N + 5], vis[MAX_N + 5], dis[MAX_N + 5];
int edg_cnt;// 此处采用链式前向星存储有向图
void add_edge(int a, int b, int c) {edg[edg_cnt].to = b;edg[edg_cnt].val = c;edg[edg_cnt].next = head[a];head[a] = edg_cnt++;
}void dijkstra(int n, int s) {  // 源点到自身的距离为0dis[s] = 0;for (int i = 1; i < n; i++) {int ind = -1;// 遍历dis数组,找到dis值最小的结点for (int j = 1; j <= n; j++) {if (vis[j]) continue;if (ind == -1 || dis[j] < dis[ind]) ind = j;}// 对于找到的结点,更新与该点的连的点到源点的值for (int j = head[ind]; j != -1; j = edg[j].next) {if (dis[edg[j].to] > dis[ind] + edg[j].val) {dis[edg[j].to] = dis[ind] + edg[j].val;}}// 标注该结点已扩展vis[ind]++;}
}int main() {memset(head, -1, sizeof(head));memset(dis, 0x3f, sizeof(dis));int n, m, s;cin >> n >> m >> s;for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;add_edge(a, b, c);}dijkstra(n, s);for (int i = 1; i <= n; i++) {// 采用或运算,当i == 1时,此时表达式为真,cout << " "将不会执行i == 1 || cout << " ";if (dis[i] == INF) {cout << int(pow(2, 31) - 1);} else {cout << dis[i];} } cout << endl;return 0;
}

  观察代码可知:算法的时间复杂度为O(n^2)。因为我们要扩展n - 1次,同时每一次扩展,我们都要遍历一遍dis数组,找到dis值最小的结点 (后续遍历以该结点为起点的边所连接的另一端的结点)
   虽然使用上述代码可以Accept上面的这道题,但是对于:P4779 【模板】单源最短路径(标准版),由于数据量较大,所有的测试用例都会超时,所以我们需要对算法进行优化改进,降低算法的时间复杂度。

三 时间复杂度优化

1. 优化策略

  第二部分我们说到,O(n^2)的算法时间复杂度过大,故需要进行优化。
  其实很简单,我们可以借助 优先队列 来优化我们的算法,首先最外面的for循环是不可必避免的,所以我们只能将遍历dis数组这一操作进行优化,我们在优先队列中存储这样的一个二元组<now, dis> 分别表示:当前是几号结点,以及该结点到源点的距离 ,对于优先队列中的元素,我们根据dis的值进行优先级排比,dis越小的二元组在优先队列的头部,这样,我们每次弹出的结点就是距离源点最近的结点,我们也就不再需要线性的遍历dis数组,所以从线性的遍历dis数组O(N),变成了使用有优先队列O(logN) 这里插一句话,优先队列的底层实现是采用二叉树。
  所以Dijkstra算法的时间复杂度由之前的O(N^2) 优化为 O(N * logN)

2. 优化后的代码

  这里以洛谷4479题为例,题目相关链接上面有。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <cstring>
#include <set>
#include <vector>
using namespace std;#define MAX_N 100000
#define MAX_M 200000/*关于Data类的说明now 表示结点编号,dis表示该结点到源点的距离重载 < ,dis越小,优先级越高
*/
struct Data {int now, dis;Data(int x, int y) : now(x), dis(y) {}bool operator< (const Data &b) const {return this->dis > b.dis;}
};struct edge {int to, val, next;
} edg[MAX_M + 5];int head[MAX_N + 5], vis[MAX_N + 5], dis[MAX_N + 5];
int edg_cnt;void add_edge(int a, int b, int c) {edg[edg_cnt].to = b;edg[edg_cnt].val = c;edg[edg_cnt].next = head[a];head[a] = edg_cnt++;
}void dijkstra(int n, int s) {dis[s] = 0;priority_queue<Data> que;que.push(Data(s, 0));for (int i = 1; i < n && !que.empty(); i++) {int ind = que.top().now;que.pop();while (vis[ind] && !que.empty()) {ind = que.top().now;que.pop();}for (int j = head[ind]; j != -1; j = edg[j].next) {if (dis[edg[j].to] > dis[ind] + edg[j].val) {dis[edg[j].to] = dis[ind] + edg[j].val;// vis[edg[j].to] = 0;que.push(Data(edg[j].to, dis[edg[j].to]));}}vis[ind]++;}
}int main() {memset(head, -1, sizeof(head));memset(dis, 0x3f, sizeof(dis));int n, m, s;cin >> n >> m >> s;for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;add_edge(a, b, c);}dijkstra(n, s);for (int i = 1; i <= n; i++) {i == 1 || cout << " ";cout << dis[i];}cout << endl;return 0;
}

  大家肯能会询问,在使用优先队列时,为什么获取队首元素之后,还要使用 while 循环,不断进行弹出操作呢?
  这是因为此次弹出的点,有可能是之前已经扩展过的点,所以我们舍弃,直接看队列中的下一个结点。
  我们现在假设,之前某一时刻以X结点为边的起点进行扩展,假设扩展了2号结点,对应边的权值依次为2,所以向优先队列中压入了<2, 2>,而之后又有一次,以Y结点为边的起点进行扩展,假设也扩展了2号结点,对应边的权值依次为1,所以向优先队列中压入了<2, 1>,显然队列中<2, 1>排在<2, 2>的前面,然后我们假设队列中的其他二元组<a, b>的b都比2大,即优先队列中的前两项为<2, 1>, <2, 2>,弹出<2, 1>后,我们将2号结点标记为1,也就是已经扩展过了的,所以之后的<2, 2>就是没有意义的,我们可以直接从队列中弹出,不做其他操作。

四 结语

  图论是数据结构和算法中难度较大的一部分,自己虽然当年数据结构考试拿了一个不错的分数,但是这并不代表自己可以熟练编程实现代码,所以,加油吧!路漫漫其修远兮,吾将上下而求索!
  另外,明天好像就出面试结果了。拜托拜托,无量天尊、如来佛祖、还有上帝、真主,保佑我过呀!Wu~~~~~

Dijkstra算法求解单源最短路径问题相关推荐

  1. Dijkstra算法求单源最短路径

    1.最短路径 在一个连通图中,从一个顶点到另一个顶点间可能存在多条路径,而每条路径的边数并不一定相同.如果是一个带权图,那么路径长度为路径上各边的权值的总和.两个顶点间路径长度最短的那条路径称为两个顶 ...

  2. dijkstra算法PHP,单源最短路径(dijkstra算法)php实现

    做一个医学项目,其中在病例评分时会用到单源最短路径的算法.单源最短路径的dijkstra算法的思路如下: 如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点.那么(Vi ...

  3. 【算法】【ACM】深入理解Dijkstra算法(单源最短路径算法)

    Dijkstra算法是用来求解从某个源点到其他各顶点的最短路径(单源最短路径). 下面的Dijkstra算法的讲解都是基于这个有向图,在遇到其他问题可以类比. 算法的基本思想: 把图中的定点分成两组, ...

  4. 【算法】Dijkstra算法(单源最短路径问题) 邻接矩阵和邻接表实现

    Dijkstra算法可使用的前提:不存在负圈. 负圈:负圈又称负环,就是说一个全部由负权的边组成的环,这样的话不存在最短路,因为每在环中转一圈路径总长就会边小. 算法描述: 1.找到最短距离已确定的顶 ...

  5. java 有向图 最短路径算法_java使用Dijkstra算法实现单源最短路径

    单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径.在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质. 一.最短路径的最优子结构性质 该性质描述为:如果P(i,j) ...

  6. Dijkstra算法(单源最短路径)

    对于有向无环图DAG,我们可以利用先拓扑排序得到顶点的一个序列,然后按照此序列进行最短路径的推进(更新当前顶点的邻接顶点的dist值),如 图的邻接表数组实现及其应用 对于有环图,通常使用的是Dijk ...

  7. 【算法】单源最短路径和任意两点最短路径总结(补增:SPFA)

    [Bellman-Ford算法] [算法]Bellman-Ford算法(单源最短路径问题)(判断负圈) 结构: #define MAX_V 10000 #define MAX_E 50000 int ...

  8. 图的基本算法(单源最短路径)

    在许多路由问题中,寻找图中一个顶点到另一个顶点的最短路径或最小带权路径是非常重要的提炼过程.正式表述为,给定一个带权有向图G = (V, E) , 顶点s到v中顶点t的最短路径为在边集E中连接s到t代 ...

  9. Dijkstra(迪杰斯特拉)算法求单源最短路径问题

    Dijkstra(迪杰斯特拉)算法求单源最短路径问题 重要的事情说三遍:代码不是我写的!代码不是我写的!代码不是我写的! 第一个算法是严蔚敏数据结构(C语言版)上写的,第二个算法是王道数据结构上写的, ...

最新文章

  1. qt 4.8.4 linux,Tslib和Qt 4.8.4与在开发板上的移植
  2. c 调用java包_C#调用java代码(IKVMC)
  3. java集合的添加方法_深入理解java集合框架之---------Arraylist集合 -----添加方法
  4. mysql replace substring 字符串截取处理
  5. 怎么获取连表查询的相同字段
  6. 【相机标定系列】相机成像的理想模型原理,相机矩阵分解
  7. phonegap文件上传(java_php),Android应用开发之使用PhoneGap实现位置上报功能
  8. 南阳理工acm 15括号匹配(二)
  9. 如何将poi数据导入arcgis
  10. 大型公司网路架构浅谈
  11. (七) 三维点云课程---ICP(Point-to-Point)
  12. Agile PLM 表结构说明
  13. python在abaqus中的应用pdf_Python语言在Abaqus中的应用
  14. uni-app 异形轮播
  15. 2022-2028年全球与中国皮卡后视镜行业市场前瞻与投资战略规划分析
  16. CSS 列表样式(ul)
  17. 矩阵(一):SVD分解
  18. python设计麻将_Python写打麻将程序
  19. R语言中如何编写自己的函数初步入门
  20. 阿龙的学习笔记---《程序员自我修养-链接、装载与库》读书笔记(一)

热门文章

  1. VS2008 Debug Error R6034
  2. Zookeeper与统一配置管理
  3. ubuntu安装zsh及环境配置
  4. 如何关闭Windows Server 2012的IE增强安全配置
  5. Redis总结:缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级
  6. MySQL数据库综合练习一
  7. windows计划任务执行,但是程序未执行
  8. 详解AUTOSAR:Green Hills Software(GHS)编译下载瑞萨RH850程序(环境配置篇—2)
  9. Debian10更改源
  10. java 打印 日历 详细 注解_Java类库 LocalDate类的简单使用(一)之打印本月的日历...