简介
Dijkstra算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(BFS思想),直到扩展到终点为止
Dijkstra不能处理带负权和带有回路的图
算法思想
-
设起点为u,引入两个集合S、U,S集合包含已求出的最短路径的点,U集合记录未求出最短路径的点以及到起点的距离
-
初始化两个集合,S集合初始时只有起点,U集合初始时为起点到其他节点的距离(详细来说是起点到本身的距离为0,起点未直接连接点的距离为INF)
-
从U集合中找出路径最短的点k,加入S集合并从U集合中删去该点
-
遍历所有节点,如果某节点v使得u到k再到v的距离小于当前u到v的距离,则更新U中相应的距离
-
循环上述3、4步骤,直至遍历结束,得到起点到其他节点的最短路径
简单分析上述思路,我们可以得到下列伪代码(紫书p359)
清除标记数组中所有点的标记
设d[s]=0,其他d[i]=INF,s是起点
循环n次{
在所有未标记节点中选出d值最小的节点k
给节点k标记
对于从k出发的所有边(k,i),更新d[i]=min{d[i],d[k]+w(k,i)}
}
其中"更新d[i]=min{d[i],d[k]+w(k,i)}"称为边(k,i)的松弛操作
代码实现
邻接矩阵
时间复杂度O(V2),空间复杂度O(V2)
#define INF 0x3f3f3f3f
const int N=1e3+10;
int G[N][N]; //邻接矩阵
int d[N]; //存起点到其他节点的最短路径
bool vis[N]; //显示当前节点的最短路径是否更新
int n; //节点个数
void Dijkstra(int u){
memset(d,0x3f,sizeof(d));
memset(vis,0,sizeof(vis));
d[u]=0;
for(int i=1;i<=n;i++){
int k,m=INF;
for(int j=1;j<=n;j++)
if(!vis[j]&&d[j]<=m){
m=d[j];
k=j;
}
vis[k]=1;
for(int j=1;j<=n;j++) d[j]=min(d[j],d[k]+G[k][j]);
}
for(int i=1;i<=n;i++) printf("%d%c",d[i],i==n?'\n':' ');
}
邻接表
实际上我们发现上面的邻接矩阵的思路和算法思想还不太一致。但是当我们写邻接表就能发现我们使用的队列相当于集合U
第一种方法是我在学习紫书时学到的,这里并没有开vector<edge>套vector<edge>的二维动态数组,而是直接用了一维的vector<edge> edges保存所有的边,然后使用二维动态数组vector<int> G[N]来保存每一个起点对应的edges数组中的下标。实际上这和链式前向星类似,但是却没有链式前向星节省空间
vector数组保存的是边的编号,有了编号之后就可以从edges数组中查到边的具体信息。这样,“对于从k出发的所有边(k,i),更新d[i]“就变成了"for(int i=0;i<G[u].size();i++) 执行边edges(G[u][i])上的松弛操作”。从整体上看,每条边恰好被检查过一次,因此松弛执行的次数恰好是m次。因此只要想办法找到"未标记的d值最小的节点k”
显然我们需要优先队列维护d[i],即d[i]越小就应该越先出队,因此需要最小堆维护的优先队列priority_queue<int,vector<int>,greater<int> > q;然而我们不但需要最小值,还需要最小值对应的编号,因为我们需要根据编号u从边集中取出G[u][i]。然后我们直接用STL中提供的pair,但是需要注意的是,我们需要make_pair(d[i],i),因为比较时是取最小的d[i],而pair的比较是先比较first再比较second
为了使用方便,下面把该算法封装到一个结构体中
typedef pair<int,int> P;
const int maxn=1e5+10; //如果需要打印出路径,那么1e5会爆内存
const int maxm=2e5+10;
struct edge{
int from,to,w;
edge(int a,int b,int c):from(a),to(b),w(c){}
}
struct Dijkstra{
int n,m; //节点数和边数
vector<edge> edges;
vector<int> G[maxn]; //记录的是每个from下的所有to边的编号
bool vis[maxn]; //判断是否标记
int d[maxn]; //起点到各节点的距离
//int p[maxn]; //最短路的上一条边
void init(int n){ //初始化结构体
this->n=n;
for(int i=0;i<=n;i++) G[i].clear();
}
void addEdge(int from,int to,int w){
edges.push_back(edge(from,to,w));
m=edges.size(); //不断更新边数
G[from].push_back(m-1); //因为每条边的编号在edges数组都是唯一的,因此只需保存编号即可(编号从0开始,因此需要m-1)
}
void dijkstra(int s){
priority_queue<P,vector<P>,greater<P> > q;
memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
d[s]=0;
q.push(make_pair(0,s)); //先构造一个s到本身的pair,很明显d[s]=0
while(!q.empty()){
P pr=q.top();
q.pop();
int u=x.second;
if(vis[u]) continue;
vis[u]=1;
for(int i=0;i<G[u].size();i++){
edge &e=edges[G[u][i]];
if(d[e.to]>d[u]+e.w){
d[e.to]=d[u]+e.w;
//p[e.to]=G[u][i]; //记录最短路
q.push(make_pair(d[e.to],e.to)); //有多条起点和终点一样的边,都要入队
}
}
}
}
void Print(){ //打印d[i]
for(int i=1;i<=n;i++) printf("%d ",d[i]);
}
};
链式前向星
了解过链式前向星我们不难发现实现思路和邻接表一模一样。但是链式前向星无论从时间还是空间都要优于上述邻接表写法,因此我们以后写邻接表就尽量使用链式前向星
下面代码还有一点点变化,这也是紫书上提到的,我们可以不使用vis标记数组,而是用"if(pr.first!=d[u]) continue;",因为我们这里是看队列中取出的d的最小值是否和d[u]相等,如果相等那么就不用判断了,因为入队后取出的一定是最短路,而我们的d[u]在上一次也更新到了最短路的值
typedef pair<int,int> P;
const int maxn=1e5+10;
const int maxm=2e5+10;
struct node{
int to,next,w;
};
struct Dijkstra{
int n;
int tot;
int head[maxn];
node edge[maxm];
int d[maxn];
void init(int n){
this->n=n;
tot=0;
}
void addEdge(int u,int v,int w){
tot++;
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
void dijkstra(int s){
priority_queue<P,vector<P>,greater<P> > q;
memset(d,0x3f,sizeof(d));
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
P pr=q.top();
q.pop();
int u=pr.second;
if(pr.first!=d[u]) continue;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
int w=edge[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
q.push(make_pair(d[v],v));
}
}
}
}
void Print(){
for(int i=1;i<=n;i++) printf("%d ",d[i]);
}
};
下面附上洛谷单源最短路径模板题P4779及参考代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define MAXN 0x7fffffff
#define INF 0x3f3f3f3f
typedef pair<int,int> P;
const int maxn=1e5+10;
const int maxm=2e5+10;
struct node{
int to,next,w;
};
struct Dijkstra{
int n;
int tot;
int head[maxn];
node edge[maxm];
int d[maxn];
//bool vis[maxn];
void init(int n){
this->n=n;
tot=0;
}
void addEdge(int u,int v,int w){
tot++;
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
void dijkstra(int s){
priority_queue<P,vector<P>,greater<P> > q;
//memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
P pr=q.top();
q.pop();
int u=pr.second;
if(pr.first!=d[u]) continue;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
int w=edge[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
q.push(make_pair(d[v],v));
}
}
}
}
void Print(){
for(int i=1;i<=n;i++) printf("%d%c",d[i]==INF?MAXN:d[i],i==n?'\n':' ');
}
};
/*struct edge{
int from,to,w;
edge(int a,int b,int c):from(a),to(b),w(c){}
};
struct Dijkstra{
int n,m;
vector<edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
//int p[maxn];
void init(int n){
this->n=n;
for(int i=0;i<=n;i++) G[i].clear();
}
void addEdge(int from,int to,int w){
edges.push_back(edge(from,to,w));
m=edges.size();
G[from].push_back(m-1);
}
void dijkstra(int s){
priority_queue< P,vector< P>,greater<P> > q;
memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
pair<int,int> pr=q.top();
q.pop();
int u=pr.second;
if(vis[u]) continue;
vis[u]=1;
for(int i=0;i<G[u].size();i++){
edge &e=edges[G[u][i]];
if(d[e.to]>d[u]+e.w){
d[e.to]=d[u]+e.w;
q.push(make_pair(d[e.to],e.to));
}
}
}
}
void Print(){
for(int i=1;i<=n;i++) printf("%d%c",d[i]==INF?MAXN:d[i],i==n?'\n':' ');
}
};*/
int main(){
int n,m,a,b,c,x;
Dijkstra dj;
scanf("%d%d%d",&n,&m,&x);
dj.init(n);
while(m--){
scanf("%d%d%d",&a,&b,&c);
dj.addEdge(a,b,c);
}
dj.dijkstra(x);
dj.Print();
}
来源:CSDN
作者:Happig丶
链接:https://blog.csdn.net/qq_44691917/article/details/104224581