【图论 & 迪杰斯特拉】Dijkstra(单源最短路径)
前言
此乃一个小 Oler 的一篇图论算法随笔,从今日后,还会进行详细的修订。
一、简单介绍 (Dijkstra)
迪杰斯特拉算法 ( Dijkstra ) 是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
定义:
Dijkstra 算法一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。
二、基本概念&用途
最短路径
-
:当图是带权图时,把从一个顶点 到图中其余任意一个顶点 的一条路径(可能不止一条)所经过边上的权值之和,定义为该路径的带权路径长度。 -
:把带权路径长度最短的那条路径称为最短路径。
备注:求解最短路径的算法通常都依赖于一种性质,即两点之间的最短路径也包含了路径上其他顶点间的最短路径。
- 带权有向图
的最短路径问题一般可分为两类:
1)单源最短路径,求图中
2)多源最短路径,求
Dijkstra 的实战性/优劣势
-
Dijkstra 算法用于有权边的图,属于单源最短路径。
-
其对于一般其他的求解单源最短路的算法,优势在于基于空间复杂度平分秋色的情况下,时间复杂度更优,但由于是贪心思想,所以图中不可出现负边权,下面的算法流程会对此进行解释。
-
其算法内涵是设置一个集合
记录已求得的最短路径的顶点,初始时把源点 放入 ,集合 每并入一个新顶点 ,都要修改源点 到集合 中顶点当前的最短路径长度值。
三、算法流程(代码实现)
DIJ之【邻接矩阵】
I.储存边权
本种方法用邻接矩阵储存任意两个邻接节点
II.Dijkstra 函数
初始化:
总共进行
①.找最短路终点
假设起始点为
从顶点集合
②.修改最短路径
修改从起始点
若
步骤如下图所示,源点为
模拟 Dijkstra 过程
III. Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int oo=0x3f3f3f3f;
const int N=10001;
int n,m,s,u,v,w;
int dist[N],arcs[N][N];
bool f[N];
void dijkstra() {
dist[s]=0; //初始化
for(int i=1;i<=n;i++) { //循环n次,每次选择一个点加入集合V
int t=-1;
for(int j=1;j<=n;j++) { //寻找值最小的顶点
if(f[j]==0&&(t==-1||dist[j]<dist[t]))
t=j;
}
f[t]=1; //标志位,把点加入集合S
for(int k=1;k<=n;k++) { //更新dist的值
if(f[k]==0&&dist[t]+arcs[t][k]<dist[k])
dist[k]=dist[t]+arcs[t][k];
}
}
return ;
}
signed main() {
scanf("%lld%lld%lld",&n,&m,&s); //输入点数、边数和起始点
memset(dist,oo,sizeof dist);
memset(arcs,oo,sizeof arcs);
while(m--) {
scanf("%lld%lld%lld",&u,&v,&w);
arcs[u][v]=min(arcs[u][v],w); //每次更新为最小值
}
dijkstra();
for(int i=1;i<=n;i++)
printf("%lld ",dist[i]);
printf("\n");
return 0;
}
DIJ之【邻接表+堆优化】
I.邻接表存图
由于要使用到优先队列堆优化 Dijkstra 的运行效率,而邻接矩阵在遍历时要对于每个顶点去找其最短的路径终点,会大大增加运行时间,所以我们的“开国元老”邻接矩阵 (落伍了) ,需要排上我们的年轻小将“车骑将军”邻接表
II.Dijkstra 遍历函数
还是
- 优化思路:在朴素版的 Dijkstra 遍历所有点比较找出距离最近的点使用优先队列priority_queue 进行优化。
①.定义优先队列
将优先队列定义成小根堆,优先队列元素为
②.初始化
初始化:
将源点
③.更新最短路径
去取出栈顶的元素,如果,堆顶节点
III. Code(PRIORITY)
#include<bits/stdc++.h>
#define int long long
#define M(x,y) make_pair(x,y)
using namespace std;
typedef pair<int,int> pll;
const int inf=2147483647;
const int N=1e6+10;
int n,m,s,x,y,z;
struct Node { //稀疏图用邻接表来存
int to,w,nxt;
Node() {
to=nxt=w=0;
}
Node(int a,int b,int c) {
to=a;
nxt=b;
w=c;
}
}adj[N];
int head[N],idx;
int dist[N];
bool st[N]; //如果true说明这个最短路径已经确定
priority_queue<pll,vector<pll>,greater<pll> >heap;
inline void add(int x,int y,int z) {
adj[++idx]=Node(y,head[x],z);
head[x]=idx;
}
void dijkstra() {
for(int i=1;i<=n;i++)
dist[i]=inf;
dist[s]=0;
heap.push(M(0,s)); //这个顺序不能倒
while(!heap.empty()) {
pll k=heap.top(); //取不在集合S(V-S)中距离最近的点
heap.pop();
int u=k.second;
int distance=k.first;
if(st[u]) continue;
st[u]=1; //把该点加入集合S
for(int i=head[u];i;i=adj[i].nxt) {
int v=adj[i].to,w=adj[i].w; //取出和ver相连的点和边权
if(dist[v]>distance+w) {
dist[v]=distance+w; //更新最短路
heap.push(M(dist[v],v)); //放入优先队列中
}
}
}
return ;
}
signed main() {
scanf("%lld%lld%lld",&n,&m,&s);
while(m--) {
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
}
dijkstra();
for(int i=1;i<=n;i++)
printf("%lld ",dist[i]);
printf("\n");
return 0;
}
四、总结
时间复杂度分析:
为图中顶点的数量, 为图中边的数目。
-
邻接矩阵储存图:
-
邻接表储存图:
(虽然修改 的时间可以减少,但是由于在 中选择最小分量的时间不变) -
堆优化:
- 若图为稀疏图,一般采用邻接表来储存,反之则适用邻接矩阵储存图。
不适用条件
- 当图中含有负边权时,Dijkstra 算法将不适用。( Dijkstra 算法是基于贪心的)。
题库
对于练习 Dijkstra 这求单源最短路的题,变式题型很多,并没有针对的练习,可以自行浅浅练练 ,如实在没有方向的也可以参考一下我练习计划的题单 洛谷 【迪杰斯塔拉】 ID:970993
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)