回顾
- Floyed算法可以求任意两点之间的最短路径,但是Dijkstra算法只能求一个结点到另一个结点的最短路径,它是一个单源的最短路径算法
- Floyed算法的时间复杂度为O(n^3),故一般情况下数据范围要求在100以内
Dijkstra算法描述
- 设起点为s,
dis[v]
表示从s到v的最短路径长度, 如dis[3] = 6
表示从起点到3号结点的最短路径为6
-
pre[v]
为v的前驱节点,用来输出路径
- 初始化:
dis[v] = ∞(v ≠ s);//初始时路径为无穷
dis[s] = 0;//s -> s的路径为0
pre[s] = 0;
for (i = 1; i <= n ; i++)
{
//1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的
//2.把u标记为已确定最短路径
//3.For循环遍历:与u相连的每个未确定最短路径的顶点v
//如果结点u的加入使得s->v的距离dis[v]变小,则更新
if (dis[u] + w[u][v] < dis[v])
{
dis[v] = dis[u] + w[u][v];
pre[v] = u;
}
}
可以通过下图来理解为什么要更新dis[v] = dis[u] + w[u][v]
:
![在这里插入图片描述](https://img-blog.csdnimg.cn/677a6931e0114df696876cb3dec41197.png#pic_center)
- 算法结束:
dis[v]
为s到v的最短距离;pre[v]
为v的前驱节点,用来输出路径。
算法的总体思想:
首先设置一个空集合,并且将所有的结点分成两类,一类是确定了最短路径的结点,另一类是未确定最短路径的结点。每次向集合中加入一个可以确定最短路的结点,然后判断会不会因为该点的加入使得某个结点到其他结点的距离变得更短,如果是,则更新最短距离。
这么多伪代码,是不是很抽象有点晕了?下面来看一个例子
Dijkstra算法举例
例:根据下图a,找出从节点1出发到各个节点的最短路径长度。
![在这里插入图片描述](https://img-blog.csdnimg.cn/4a624887141143d98bf9490f12cf0f40.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/b3724f176bd24500bd71d0bce933d2f7.png#pic_center)
-
第三轮循环发现除1、2号结点外,dis[3] = 3
最小,将结点3标记成已经确定最短路径的结点(3号结点的加入会影响1号结点到4和5号结点的距离)。另外还需要根据图a对其余绿色结点做出修改,使得dis[4] = 4
,dis[5] = 4
。
![在这里插入图片描述](https://img-blog.csdnimg.cn/24e334f9e15d44e1a795e1555a5b92de.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/25a5462285a74c1196129dcb83888e51.png#pic_center)
-
第四轮循环发现只有4号和5号结点还没有加入"集合",因为dis[4] = dis[5] = 4
,又因为4号结点无法直接到达5号结点,故可以将4号结点直接加入,不需要对dis数组做出修改。
-
最后只剩下一个五号结点没有加入集合,将其加入即可,从而得到1号结点到其余各个结点之间的最短距离如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/dbe55b0e846e43ce8e3dd0676b8c009d.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/d00738e344af4e2bb12d9cdbce3be4db.png#pic_center)
看到这个粉绿粉绿的东西,突然…![在这里插入图片描述](https://img-blog.csdnimg.cn/f0f6633288b141119f17bc82e4c95a39.png#pic_center)
圣斗士星矢!仙女座登场!!
![在这里插入图片描述](https://img-blog.csdnimg.cn/9a289cd578c4483eab3e4307882a4346.png#pic_center)
迪杰斯特拉代码模板
//迪杰斯特拉算法模板
//求源点s到其余各点的最短路径
void dijkstra(int s)
{
memset(vis,0,sizeof vis);//1表示已经确定最短路径的点,0表示还未确定最短路径的点
//初始时将s到所有点的路径设置为无穷
memset(dis,0x3f,sizeof dis);
dis[s] = 0;//结点s到s的距离为0
while (1)//直到将所有的点都设置为已经访问,结束循环
{
int u = 0,min = INF;
for (int j = 1;j <= n;j++)//用j遍历dis数组,每次遍历都会确定一个最短路的结点
{
if (!vis[j] && min > dis[j])//当前结点未被访问过
{
u = j;//u保存确定的最短路结点
min = dis[j];//保存当前dis数组中的最短路径
}
}
if (u == 0) return;//所有的点都被访问过,结束循环
vis[u] = 1;//将其设置为已经访问过的点
for (int v = 1;v <= n;v++)//最后的循环用来更新dis数组
if (dis[v] > dis[u] + w[u][v])//将u结点加入之后路径变短
dis[v] = dis[u] + w[u][v];
}
}
Dijkstra算法总结
Dijkstra算法的时间复杂度为O(n^2),它是用来计算从一个点到其他所有点的最短路径的算法,是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。**它不能处理存在负边权的情况。**那么,为什么迪杰斯特拉算法不能处理负的边权呢?举个例子:
![在这里插入图片描述](https://img-blog.csdnimg.cn/70f157c089324b17b77230b32fc1ca20.png#pic_center)
在上图中,结点2到结点3的权值为-4,显然从起点1到3的最短路径是-2,即1 → 2 → 3,但是dijskstra
在第二轮循环开始时会找到当前dis[i]
最小的点3,并标记它为白点。这时的dis[3] = 1
,然而1却不是从起点到点3的最短路径。因为3已被标记为白点,最短路径值dis[3]
不会再被修改了,所以我们在边权存在负数的情况下得到了错误的答案。
- 第一次循环
- 第二次循环
例题以及AC代码
Acwing 849 Dijkstra求最短路 I
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int n,m;
const int N = 510;
const int INF = 0x3f3f3f3f;
int dis[N];//表示源点到其余各点的最短距离
int w[N][N];//w[i][j]表示i到j的路径
int vis[N];//vis[i]=0表示当前结点没被访问过,否则已经被访问过
//迪杰斯特拉算法模板
//求源点s到其余各点的最短路径
void dijkstra(int s)
{
memset(vis,0,sizeof vis);//1表示已经确定最短路径的点,0表示还未确定最短路径的点
//初始时将s到所有点的路径设置为无穷
memset(dis,0x3f,sizeof dis);
dis[s] = 0;//结点s到s的距离为0
while (1)//直到将所有的点都设置为已经访问,结束循环
{
int u = 0,min = INF;
for (int j = 1;j <= n;j++)//用j遍历dis数组,每次遍历都会确定一个最短路的结点
{
if (!vis[j] && min > dis[j])//当前结点未被访问过
{
u = j;//u保存确定的最短路结点
min = dis[j];//保存当前dis数组中的最短路径
}
}
if (u == 0) return;//所有的点都被访问过,结束循环
vis[u] = 1;//将其设置为已经访问过的点
for (int v = 1;v <= n;v++)//最后的循环用来更新dis数组
if (dis[v] > dis[u] + w[u][v])//将u结点加入之后路径变短
dis[v] = dis[u] + w[u][v];
}
}
int main()
{
memset(w,0x3f,sizeof w);
scanf("%d%d",&n,&m);
for (int i = 0;i < m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
w[x][y] = min(w[x][y],z);//有重边的情况,取最小值
}
//(dij可以省略)for (int i = 1;i <= n;i++) w[i][i] = 0;
dijkstra(1);
if (dis[n] > INF / 2) printf("-1\n");
else printf("%d\n",dis[n]);
return 0;
}