迪杰斯特拉

  • 迪杰斯特拉算法分析
    • 迪杰斯特拉(朴素版)
    • 迪杰斯特拉堆优化(邻接表存图)
    • 迪杰斯特拉堆优化(链式前向星存图)
    • 最短路——spfa(链式前向星存图)

迪杰斯特拉算法分析

一般用三种数据结构存图,即邻接矩阵,邻接表,链式前向星

1.邻接矩阵

用二维数组储存即可int graph[maxn][maxn];, graph[i][j]储存结点i到j边的权值

优点
适合稠密图,编码非常简短,对边的存储,查询,更新等操作又快又简单,只需要一步就能访问和修改。
缺点

  • 存储复杂度O(V^2)太高
  • 一般情况下不能存储重边

2.邻接表

一些规模大的稀疏图一般用邻接表存储。

优点
他的优点是存储效率非常高,只需要与边数成正比的空间,存储的复杂度是O(V+E),几乎已经达到了最优复杂度,而且能存储重边
缺点
编程比邻接矩阵麻烦一些,访问和修改也慢一些

一般用STL的vector(动态数组)实现了邻接表
  • 首先对边赋值
  • 用e[i]:存第i个结点连接的所有边
  • 把边(a,b)存到结点a的邻接表中
  • 结点u的邻居有e[u].size()个

3.链式前向星

链式前向星是在邻接表的基础上优化的,对于一些空间极其紧张的题,用链式前向星是最好的,它用静态数组模拟邻接表,没有任何浪费,因此它是空间效率最高的存储的方法

  • 对于边而言,边的终点是to,权值是w,下一个边next,起点放在head[]中
  • head[u]指向结点u的第一个边的存储位置
  • 用cnt记录edge[]的末尾的位置,新加入的边放在末尾
    链式前向星的优点是存储效率高,程序简单,能存储重边,缺点是不方便做删除操作

适用范围
迪杰斯特拉算法适用于——单元最短路径问题,单终点最短距离,单对顶点最短路径问题,每对顶点间最短路径问题

基本思想

  • Dijkstra算法:一个安路径长度递增地次序产生最短路径的算法
  • 每次扩展一个距离最短的点,更新与其相邻的点的距离
  • 当所有边的权值都为正数时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,保住了算法的正确性。如果存在负权值的路径,那么考虑spfa算法跑最短路。
  • 有向图和无向图都可以使用这个算法,无向图的每条边可以看成相反的两条边
  • 引入松弛操作,让源点s到顶点的距离dis[i]取一个很大的值,然后不断减小dis[i],当所有的dis[i]不能再减小时,就求出了s到所有点的最短路径。松弛操作的目的就是减小dis[i]的值,如果从s到达i有更优的路径则更新dis[i]。
if (dis[v] > dis[u] + e[i].w){dis[v] = dis[u] + e[i].w;  //松弛操作q.push((node){dis[v], v}); //把新遍历的到的点加入堆中}


dijkstar的原理与流程

  • dijkstra本质上的思想是贪心,它只适用于不含负权边的图.
  • 我们把点分成两类,一类是已经确定最短路径的点,称为"白点",另一类是未确定最短路径的点,称为"蓝点"
  • 初始化dis[start] = 0,dis[start]=0,其余节点的dis值为无穷大.
  • 找一个disdis值最小的蓝点x,把节点x变成白点.
  • 遍历x的所有出边(x,y,z),若dis[y] > dis[x] + z,则令dis[y] = dis[x] + z
  • 重复2,3两步,直到所有点都成为白点…
  • 时间复杂度为O(n^2)

举例
​ 友情提示以下图片参考洛谷—最短路在此鸣谢~
1.(令start = 1)
2.开始时我们把dis[start]初始化为0,其余点初始化为inf

第一轮循环找到dis值最小的点1,将1变成白点,对所有与1相连的蓝点的dis值进行修改,使得dis[2]=2,dis[3]=4,dis[4]=7

第二轮循环找到dis值最小的点2,将2变成白点,对所有与2相连的蓝点的dis值进行修改,使得dis[3]=3,dis[5]=4

第三轮循环找到dis值最小的点3,将3变成白点,对所有与2相连的蓝点的dis值进行修改,使得dis[4]=4

接下来两轮循环分别将4,5设为白点,算法结束,求出所有点的最短路径

为什么dijkstra不能处理有负权边的情况

  • 2到3的边权为-4,显然从1到3的最短路径为-2 (1->2->3)…但在循环开始时程序会找到当前dis值最小的点3,并标记它为白点
  • 这时的dis[3]=1,然而1并不是起点到3的最短路径.因为3已经被标为白点,所以dis[3]不会再被修改了.我们在边权存在负数的情况下得到了错误的答案.

下面有一个例题(单源最短路径的模板题)

题目描述
编程实现Dijkstra算法,求一个有向加权图中,从源点出发到其他各个顶点的最短路径。

输入
第1行第1个值表示顶点个数,第2个值表示边个数;第2行开始为边(两个顶点,边的起点和终点)及权重。

输出
顶点0到每一个顶点的最短路径长度。

样例输入

5 7
0 1 10
0 3 30
0 4 100
1 2 50
2 4 10
3 2 20
3 4 60

样例输出
0 10 50 30 60

PS:这个题目是一个模板题,但是它默认源点是0点,所以在输入的时候没有输入源点,一般的代码都是输入三个数,顶点数,边数,与源点,代码基本一样,只要改一下一点点代码就行——如果想要源点从键盘输入,那么增加一个输入,并把起点放入队列或者动态数组里面就行。

迪杰斯特拉(朴素版)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct node
{int to, dis;bool operator<(const node x) const{return dis > x.dis;}
};
vector<node> w[maxn];
int dis[maxn], vis[maxn], n;
void dijkstra()
{memset(dis, inf, sizeof(dis));memset(vis, 0, sizeof(vis));dis[0] = 0, vis[0] = 1;for (int i = 0; i < w[0].size(); ++i){int to = w[0][i].to;dis[to] = w[0][i].dis;}for (int i = 1; i < n; ++i){int minn = n;for (int j = 1; j < n; ++j){//找到未确定的距离0最近的点if (!vis[j] && dis[j] < dis[minn])minn = j;}vis[minn] = 1;//标记被确定的点for (int i = 0; i < w[minn].size(); ++i){int to = w[minn][i].to;//从minn点更新其他点if (!vis[to] && dis[to] > dis[minn] + w[minn][i].dis)//如果发现路径0->minn->to比之前到to的路径的更优,更新dis[to] = dis[minn] + w[minn][i].dis;}}
}
int main()
{int m, a, b, c, v, num;while (~scanf("%d%d", &n, &m)){node now;for (int i = 1; i <= m; ++i){scanf("%d%d%d", &a, &b, &now.dis);now.to = b;w[a].push_back(now);//vector存边,(数组也可以)now.to = a;w[b].push_back(now);}dijkstra();for (int i = 0; i < n; ++i)printf("%d%c", dis[i], i == n - 1 ? '\n' : ' ');}return 0;
}

定义一个优先队列加结构体重载小于号,就相当于一个小根堆了,重载把大的放在前面,然后再把排好序的数据放入优先队列那么大的就在根底了,也就形成了一个小根堆

迪杰斯特拉堆优化(邻接表存图)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct node
{int to, dis;bool operator<(const node x) const{return dis > x.dis;}
};
vector<node> w[maxn];
int dis[maxn], vis[maxn], n;
void dijkstra()
{memset(dis, inf, sizeof(dis));memset(vis, 0, sizeof(vis));dis[0] = 0;priority_queue<node> pq;node now, ne;now.to = 0, now.dis = 0;pq.push(now);while (!pq.empty()){now = pq.top(), pq.pop();if (vis[now.to])continue;vis[now.to] = 1;for (int i = 0; i < w[now.to].size(); ++i){int to = w[now.to][i].to;if (!vis[to] && dis[to] > now.dis + w[now.to][i].dis){dis[to] = now.dis + w[now.to][i].dis;ne.dis = dis[to];ne.to = to;pq.push(ne);}}}
}
int main()
{int m, a, b, c, v, num;while (~scanf("%d%d", &n, &m)){node now;for (int i = 1; i <= m; ++i){scanf("%d%d%d", &a, &b, &now.dis);now.to = b;w[a].push_back(now); //vector存边,(数组也可以)now.to = a;w[b].push_back(now);}dijkstra();for (int i = 0; i < n; ++i)printf("%d%c", dis[i], i == n - 1 ? '\n' : ' ');}return 0;
}

迪杰斯特拉堆优化(链式前向星存图)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int inf = 0x3f3f3f3f;
struct edge
{int to, next, w;
} e[maxn];
int head[maxn], cnt = 1, n, m, s, vis[maxn], dis[maxn];
struct node
{int w, now;bool operator<(const node x) const{ //大根堆,大的在前return w > x.w;}
};
priority_queue<node> q;       //定义一个大根堆
void add(int u, int v, int w) //链式前向星加边
{e[cnt].to = v;e[cnt].w = w;e[cnt].next = head[u]; //存储该点的下一条边head[u] = cnt++;       //更新目前该点的最后一边
}
void dijkstra()
{for (int i = 0; i < n; ++i)dis[i] = inf;dis[s] = 0; //初始化q.push(node{0, 0});while (!q.empty()) //如果堆栈为空,那么所有点都已经更新{node x = q.top();q.pop();int u = x.now; //记录堆顶并将其弹出if (vis[u])continue;vis[u] = 1;for (int i = head[u]; i; i = e[i].next) //搜索堆顶所有的连边{int v = e[i].to;if (dis[v] > dis[u] + e[i].w){dis[v] = dis[u] + e[i].w;  //松弛操作q.push((node){dis[v], v}); //把新遍历的到的点加入堆中}}}
}
int main()
{while (~scanf("%d%d", &n, &m)){for (int i = 1; i <= m; ++i){int x, y, z;scanf("%d%d%d", &x, &y, &z);add(x, y, z);}dijkstra();for (int i = 0; i < n; ++i)printf("%d ", dis[i]);printf("\n");}return 0;
}

最短路——spfa(链式前向星存图)

SPFA算法由于它上限 O(NM) = O(VE)的时间复杂度,被卡掉的几率很大.在算法竞赛中,我们需要一个更稳定的算法:dijkstra.
spfa算法参考链接

#include <bits/stdc++.h>
const long long inf = 2147483647;
const int maxn = 10005;
const int maxm = 500005;
using namespace std;
int n, m, s, num_edge = 0;
int dis[maxn], vis[maxn], head[maxm];
struct Edge
{int next, to, dis;
} edge[maxm];                           //结构体表示静态邻接表
void addedge(int from, int to, int dis) //邻接表建图
{                                       //以下是数据结构书上的标准代码,不懂翻书看解释edge[++num_edge].next = head[from]; //链式存储下一条出边edge[num_edge].to = to;             //当前节点编号edge[num_edge].dis = dis;           //本条边的距离head[from] = num_edge;              //记录下一次的出边情况
}
void spfa()
{queue<int> q; //spfa用队列,这里用了STL的标准队列for (int i = 0; i < n; i++){dis[i] = inf; //带权图初始化vis[i] = 0;   //记录点i是否在队列中,同dijkstra算法中的visited数组}q.push(0);dis[0] = 0;vis[0] = 1; //第一个顶点入队,进行标记while (!q.empty()){int u = q.front(); //取出队首q.pop();vis[u] = 0;                                //出队标记for (int i = head[u]; i; i = edge[i].next) //邻接表遍历,不多解释了(也可用vector代替){int v = edge[i].to;if (dis[v] > dis[u] + edge[i].dis) //如果有最短路就更改{dis[v] = dis[u] + edge[i].dis;if (vis[v] == 0) //未入队则入队{vis[v] = 1; //标记入队q.push(v);}}}}
}
int main()
{cin >> n >> m;for (int i = 0; i < m; i++){int f, g, w;cin >> f >> g >> w;addedge(f, g, w); //建图,有向图连一次边就可以了}spfa(); //开始跑spfafor (int i = 0; i < n; i++)if (s == i)cout << 0 << " "; //如果是回到自己,直接输出0elsecout << dis[i] << " "; //否则打印最短距离return 0;
} //结束
不必太纠结当下,也不必忧郁未来,当你经历过一些事情后,眼前的风景已经和从前不一样了。人生没有无用的经历,只要我们一直向前走,天总会亮。我想人生的每一段相遇,都会有它的意义,爱你的人带给你温暖,你爱的人带给你欢愉,不爱你的人教会你成长,而你不爱的人教会了你疏离,地球上有809个岛屿 ,204个国家 ,77亿人,相遇的概率只有2920万分之一,我和你相遇,已经是无比的幸运~

迪杰斯特拉最全详解(朴素版,堆优化+邻接表存图/链式前向星存图)相关推荐

  1. 迪杰斯特拉(Dijkstra)算法详解

    迪杰斯特拉(Dijkstra)算法详解 在讲解迪杰斯特拉算法之前先讲解一下什么是最短路径: [图一] 假如我要求从A点到D点的最短路径,我们用肉眼可以很快速找出A–>C–>E–>D就 ...

  2. 链式前向星存图(有图详解)

    链式前向星:既然是链式那么肯定和链表相关,前向星是每次指向都向前 链式前向星存图是以边为中心,并不是以结点为中心,它记录的是边的一些属性,包括边边的id.头节点.尾结点.权值.边的指向! 边的指向是遍 ...

  3. PTA: 旅游规划 [狄杰斯特拉+堆优化+链式前向星]

    题目 输入样例: 4 5 0 3 0 1 1 20 1 3 2 30 0 3 4 10 0 2 2 20 2 3 1 20 输出样例: 3 40 思路 狄杰斯特拉几乎算是模版题.练了一下链式前向星. ...

  4. PTA: 天梯地图 [狄杰斯特拉+堆优化+链式前向星]

    题目 输入样例1: 10 15 0 1 0 1 1 8 0 0 1 1 4 8 1 1 1 5 4 0 2 3 5 9 1 1 4 0 6 0 1 1 7 3 1 1 2 8 3 1 1 2 2 5 ...

  5. 图论:SPFA 算法详解( 算法竞赛入门到进阶) HDU 2544 链式前向星 【提供ACM模板+图解,不会都难!】

    文章目录 SPFA简介 链式前向星介绍 SPFA算法思路详细 模板-链式前向星 参考书籍:算法竞赛入门到进阶 罗勇军 SPFA简介 用队列处理Bellman-Ford算法可以很好地优化,这种方法叫做S ...

  6. Oracle分区详解和创建,数据库优化-Oracle表分区的创建和分类

    Oracle的表分区功能通过改善可管理性.性能和可用性,从而为各式应用程序带来了极大的好处.通常,分区可以使某些查询以及维护操 当表中的数据量不断增大,查询数据的速度就会变慢,应用程序的性能就会下降, ...

  7. 前向星和链式前向星(详解+模板)

    前向星和链式前向星 参考博客:深度理解链式前向星 什么是前向星 前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的 ...

  8. 迪杰斯特拉算法实现-Dijkstra 简单版

    迪杰斯特拉算法实现单源最短路 使用了 邻接表来存放图的信息,使用了优先级队列. #include <iostream> #include<queue> #include< ...

  9. 再会迪杰斯特拉(Dijkstra)

    迪杰斯特拉算法 算法说明 迪杰斯特拉算法用来求解某一个起点到以其他所有点为终点的最短路径长度: 算法思路-贪心算法 以下图为例 指定一个节点(即起点),例如计算"A"到其他节点的最 ...

最新文章

  1. Androguard 的交叉引用说明
  2. 数据库的一些基本知识
  3. 贵州师范学院计算机2级报名,贵州省2021年上半年计算机二级报名时间
  4. 7-17 汉诺塔的非递归实现 (25 分)(思路分析)
  5. 【产品干货】经典营销模型的产品化介绍
  6. 有效的云安全态势始于三个步骤
  7. 效率源希捷硬盘远程维修案例-K9盘卡死引起的F级处理
  8. Oracle全备增量备份脚本,ORACLE-RMAN:备份脚本(全库,增量)
  9. 天勤数据结构代码——排序
  10. 冰点下载器手机版apk_冰点下载器官网
  11. 华为交换机忘记密码怎么办啊,华为交换机登陆密码忘了怎么办
  12. java pdf打印_Java 打印PDF文档的3种方法
  13. 最小外接矩形--最大内接矩形
  14. 在postgre数据库中,强制删除一个数据库
  15. linux学习笔记 -- 系统编程
  16. 茶文化入门 --茶分类
  17. 一个简洁的背单词、背短语python程序——英文背记系统(自用)
  18. Ubuntu18.04网络连接图标上显示问号
  19. 通过winform+模拟登录实现快速一键登录到人才招聘网站
  20. servu ip段访问_高防IP是如何防御DDOS/CC攻击的,防御原理是什么?

热门文章

  1. 记一次vue-element-admin路由问题
  2. 不求人自己鼓捣基于TypeScript从零重构axios
  3. C++学习之第十一天-多态、虚函数
  4. python学习 字符串函数
  5. java根据类名创建对象_java反射之根据全类名创建对象
  6. 扩展欧几里得算法( 数学问题 + 扩展欧几里得算法 + 裴蜀定理 )
  7. for、while、until 循环语句
  8. C#中的PerformanceCounter类
  9. 再刷网友修改版1.90.96WWE
  10. busybox启动流程简单解析:从init到shell login