n表示点的数量 m表示边的数量
朴素版迪杰斯特拉 O(n^2)适合稠密图(n ^2约等于m)
题目链接:https://www.acwing.com/problem/content/851/
共有n个点 起点是1 剩余n-1个点的距离未知 所以只需循环n-1次就可以确定所有点到1的最短距离 n点的最短距离距一定出来了。每次循环从没有确定最短路的点集(标记为0)中找到最近的那个点 再用这个点去更新其他与他相连的点(自环应该是没有影响的,走过自环到这一点一定不是最短路 因为边权大于0)
#include<iostream>
#include<cstring>
using namespace std;
int n,m,g[510][510];//一定要开大一点!!!
int dist[510];
bool st[510];//已经确定最短路了就标记为1
void dij()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;//这里一定不能标记点1,还要用一点去更新其他点
for(int i=1;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)//寻找没有确定最短路点中的最近的点
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
for(int j=1;j<=n;j++)//更新其他点
dist[j]=min(dist[t]+g[t][j],dist[j]);
st[t]=1;
}
if(dist[n]==0x3f3f3f3f) cout<<-1<<endl;
else cout<<dist[n]<<endl;
return ;
}
int main()
{
memset(g,0x3f,sizeof g);//因为有重边 初始化最大 取最小
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);//稠密图 用矩阵比较好写
}
dij();
return 0;
}
堆优化迪杰斯特拉 O(mlogn)适合稀疏图
优化思路 是在朴素版的基础上 通过优先队列(又叫堆)只用O(1)来寻找最小值
其他地方与朴素版是一样的 就是每次从队列弹出的点有可能已经求得最短路了 continue 就行了
链接:https://www.acwing.com/problem/content/852/
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
typedef pair<int,int> PII;
int n,m,h[maxn],ne[maxn],e[maxn],w[maxn],dist[maxn],idx;
void add(int a,int b,int c)
{
e[idx]=b; w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool st[maxn];
int dij()
{
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;//PII类型的优先队列,优先排序first
heap.push({0,1});
while(!heap.empty())
{
auto x=heap.top();
heap.pop();
int u=x.second;
if(st[u]) continue;
for(int i=h[u];i!=-1;i=ne[i])//更新距离
{
int v=e[i];
if(dist[v]>dist[u]+w[i])
{
dist[v]=dist[u]+w[i];
heap.push({dist[v],v});
}
}
st[u]=1;//别忘了标记
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
memset(h,-1,sizeof h);//初始化不能忘
memset(dist,0x3f,sizeof dist);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dij();
return 0;
}
Bellman-Ford算法 O(nm) 可以做有边数限制的负边权最短路
链接:https://www.acwing.com/problem/content/submission/855/
如果有边数限制k次 外层循环看k次,如果没有的话就循环n次;内层循环一直都是循环m次 一直去更新最小值 就可以求得最短路了(虽然不知道为什么,但就是这么神奇)还有一点是每次循环有可能发生串联 外层循环就是表示经过的边数
假如有这种情况在第1次循环是表示经过一条边到达点的最小距离 点1到点3应该是等于4 但是如果点2更新过了再去更新点3 就会更新为 3 就相当于串联一样没有边数限制的话结果不会有影响 有的话就有可能了
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100010;
int idx,dist[maxn];
struct node
{
int u,v,w;
}ed[maxn];
int main()
{
memset(dist,0x3f,sizeof dist);//初始化
int n,k,m;
cin>>n>>m>>k;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
ed[idx++]={a,b,c};
}
dist[1]=0;
for(int i=0;i<k;i++)
{
int d[maxn];
for(int i=1;i<=n;i++)
d[i]=dist[i];
for(int j=0;j<m;j++)
{
int u=ed[j].u,v=ed[j].v,w=ed[j].w;
if(dist[v]>d[u]+w)
dist[v]=d[u]+w;
}
}
if(dist[n]>0x3f3f3f3f/2) cout<<"impossible"<<endl;//如果点n到不了但是有到点n的负权边 dist[n]就不会是inf了但是它再怎么减也不会小于inf/2
else cout<<dist[n]<<endl;
return 0;
}
spfa (bellman算法的优化版) 一般是O(m)最坏情况下可以卡成O(nm)
可以解决负边权问题但是没有边数限制 一般要比bellman算法要快
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int h[maxn],e[maxn],ne[maxn],w[maxn],idx,dist[maxn];
bool st[maxn];//标记是否在队列里 切记!!!
int n,m;
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void spfa()
{
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=1;
while(q.size())
{
int u=q.front();
q.pop();
st[u]=0;//弹出后要抹去标记
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(dist[v]>dist[u]+w[i])
{
dist[v]=dist[u]+w[i];
if(!st[v])//不在队列 入队并标记
{
q.push(v);
st[v]=1;
}
}
}
}
if(dist[n]>=0x3f3f3f3f/2) cout<<"impossible";
else cout<<dist[n];
}
int main()
{
memset(dist,0x3f,sizeof dist);
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
//if(b==n) cout<<a<<endl;
add(a,b,c);
}
spfa();
return 0;
}
spfa可以来判断是否有负环 开一个数组来表示到某一点的最短路经过的点数如果点数大于n 有抽屉原理可知路径上一定有重复点 就一定有负环了
弗洛伊德算法 多源最短路唯一知道的也是最简单的算法 O(n^3)基于动态规划
k是状态 所以最外层枚举 (不是很理解)
链接:https://www.acwing.com/problem/content/856/
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=250,inf=0x3f3f3f3f;
int g[maxn][maxn];
int n,m,k;
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)//初始化
for(int j=1;j<=n;j++)
if(i!=j)g[i][j]=inf;
else g[i][j]=0;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
while(k--)//k次询问
{
int a,b;
cin>>a>>b;
if(g[a][b]<0x3f3f3f3f/2)//负边权的原因
cout<<g[a][b]<<endl;
else puts("impossible");
}
}