最短路问题 n点数 m边数
- 单源最短路
- 所有边权都是正数 朴素dijkstra算法(适合稠密图) O(n*n) 堆优化Dijjkstra算法(适合稀疏图) O(mlogn)
- 存在负权边 Bellman-Ford O(nm) SPFA 一般O(m) 最坏O(nm)
- 多源汇最短路 Floyd O(n*n*n)
朴素dijistra算法过程:假设数组s里面存已经找到的距离最小的点
- d[ 1 ]=0,d[i]=无穷(一个很大的数)
- for从1到n遍历
- t= s数组以外的距离最小的点
- 把t加入s
- 利用t更新s里面的其他最短距离
稠密图用邻接矩阵存,稀疏图用邻接表存储,dijistra适合稠密图
单源最短路就是找从起点出发到任意一点的最短路径!
#include<iostream>
#include<cstring>
using namespace std;
const int N=510;
int n,m;
int dist[N];//保存每个点到源点的距离
bool st[N];//每个点的状态 是否找到了最小距离
int g[N][N];
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;//要记得这两个顺序不能写反!先memset再赋特定值
for(int i=0;i<n;i++)
{ //第一步
int t =-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
//第二步
st[t] = true;
//第三步
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);
}
int t = dijkstra();
printf("%d\n",t);
return 0;
}
输入:
3 3
1 2 1
2 3 2
1 3 4
输出:
3
堆优化版的dijkstra算法 mlogn 解决稀疏图 最短路问题
用priority_queue 来表示堆
//堆优化dijkstrta求最短路
#include <iostream>
#include <cstring>
#include<queue>
using namespace std;
const int N=100010;//稀疏图 邻接表存矩阵
typedef pair<int,int> PII;
int n,m;
int dist[N];
bool st[N];
int h[N],e[N],ne[N],w[N],idx;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
priority_queue<PII,vector<PII>,greater<PII>> q;
q.push({0,1});
while(q.size())
{
auto t = q.top();
q.pop();
int ver = t.second,distance = t.first;
if(st[ver]) continue;
else st[ver] = true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>distance+w[i])
{
dist[j]=distance+w[i];
q.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t = dijkstra();
printf("%d\n",t);
return 0;
}
Bellman—Ford 算法 解决带负权边的最短路问题 矩阵存储方法不做要求
- 遍历n次
- 遍历每条边,更新每个点的最短路
有负环只能用它 不能用SPFA
要解决有限条边的最短路问题时只能用此方法
//BELLMAN FORD算法
#include<iostream>
#include<cstring>
using namespace std;
const int N=510,M=100010;
int n,m,k;
int dist[N],backup[N];
struct edge{
int a,b,w;
}Edges[M];
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for(int i=0;i<k;i++)
{
memcpy(backup,dist,sizeof dist);
for(int j=1;j<=m;j++)
{
int a= Edges[j].a,b=Edges[j].b,w=Edges[j].w;
dist[b] = min(dist[b],backup[a]+w);
}
}
if(dist[n]>0x3f3f3f3f/2) return -1;
return dist[n];
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
Edges[i]={a,b,w};
}
int t = bellman_ford();
if(t==-1) puts("impossible");
else printf("%d\n",t);
return 0;
}
SPFA 对bellmanford优化
spfa 算法是单源最短路问题中限制最少的算法。只要图中没有负环,就可以用 spfa 算法。绝大部分情况下(99.9%),最短路问题都没有负环。如果出题人没有对 spfa 限制,那么 spfa 比 dijkstra 更快。建议:正权边使用 dijkstra 算法,负权边使用 spfa 算法。spfa 算法其实是对 bellman_ford 算法做了优化:dist[b] 要想被修改,只有在 dist[a] 被修改的情况下。因此,若 dist[a] 被修改,同时如果满足 dist[b] > dist[a] + w,则修改 dist[b]。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
int n,m;
int e[N],ne[N],h[N],w[N],idx;
int dist[N];
bool st[N];//st存元素是否在队列,以防重复元素,剪枝
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;
while(q.size())
{
int t = q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t =spfa();
//此处不需要除以2,也不要backup,是因为spfa中不存在串联更新问题,他每次只会更新queue中的紧邻点
if(t==0x3f3f3f3f) puts("impossible");
else printf("%d\n",t);
return 0;
}
spfa算法判断负环
if cnt[j] >= n 则存在负环
floyd解决多源最短路问题 时间复杂度O(n^3)
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e5+10,INF=1e9;
int n,m,k,a,b,c;
int d[N][N];
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j) d[i][j]=0;
else d[i][j]=INF;
}
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
d[a][b] = min(d[a][b],c);
}
floyd();
while(k--)
{
scanf("%d%d",&a,&b);
if(d[a][b]>0x3f3f3f3f/2) puts("impossible");
else printf("%d\n",d[a][b]);
}
return 0;
}