注明:摘自acwing算法基础课
- 朴素 Dijkstra
时间复杂度:O(n*n)
适用条件:所有边都为正数
思想:贪心思想,每次找到离起点最近的一个点,并用该点更新其他点到起点的距离
步骤:
1. dist[1]=0, dist[i]=正无穷
2.for i:1~n
找到t t:不在st中的距离最短的点,st[]:存放已经确定最短距离的点
3.用t更新其他的点,判断dist[t]+g[t][j]和dist[j]的大小,更新dist[j]
4.将t加入st[]
模板:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=510;
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n-1;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]=true;
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d",&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);
}
printf("%d\n",dijkstra());
return 0;
}
- 堆优化版Dijkstra
其他同上
时间复杂度:m log n(邻接表+stl优先队列,m为边数,n为节点数)
优化:用小根堆找最短边,堆每次更新时间复杂度为log n
步骤:
1.压开始结点入堆
2.while循环:堆中元素
找到最短边:弹出堆首元素,不在st[]中的距离最短的边
将该边所在节点加入st[]
用该节点更新其他节点:遍历邻接表
模板:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=1e6+10;
int n,m;
int h[N],e[N],w[N],ne[N],idx;
int dist[N];
bool st[N];
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>> heap;
heap.push({0,1});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver]) continue;//虽然有冗余问题,但该行可以避免
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j]=dist[ver]+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
printf("%d",dijkstra());
return 0;
}
- Bellman-Ford算法
时间复杂度:O(n*m)
适用条件:
1.边权为正为负都可以,可以有负环,但是有负权回路不一定存在最短路。
2.有限制,要求最多不经过k条边。
思想:两重循环,第一层循环k次(k根据限制),第二层循环m次,松弛所有边
步骤:
for n次 //迭代n次后,实际意义是从1号点经过不超过n条边的最短路的距离
for 所有边 松弛操作(每次迭代会松弛所有边,会有一个点松弛到极限)
模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510,M=10010;
struct Edge
{
int a,b,c;
}edges[M];
int n,m,k;
int dist[N];
int backup[N];//存上一次迭代后的结果
void 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=0;j<m;j++)
{
int a=edges[j].a,b=edges[j].b,w=edges[j].c;
dist[b]=min(dist[b],backup[a]+w);
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++)
{
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edges[i]={a,b,w};
}
bellman_ford();
if(dist[n]>0x3f3f3f3f/2) puts("impossible");
else printf("%d\n",dist[n]);
return 0;
}
- spaf求最短路
时间复杂度:一般O(m),被卡O(nm)
适用条件:边权为正为负都可以,可以有负环,但此时无最短路
思想:在bellman-ford思想的基础上,dist[b]=min(dist[b],backup[a]+w),只有a被更新过,b才有可能被更新;
首节点入队,被更新过的节点且不在队列中的节点入队,弹出队首节点(被更新过的点)
和Dijkstra区别:dijkstra是一锤子买卖 ,点被选中了之后的距离不可能在被更新了,而spfa是每条边都可能会被再次更新,要不断的向队列中加入新的更新信息
模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],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;//spfa中每个点可能需要更新多次。
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])//每个点不一定只被更新一次,即st[j] == true的点可能被再次更新。
{
dist[j]=dist[t]+w[i];
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int t=spfa();
if(t==0x3f3f3f3f) puts("impossible");
else printf("%d\n",t);
return 0;
}
- spfa判断负环
时间复杂度:
思想:
在spfa 判断最短路的基础上判断负环
若没有负环,到第n个点共需要更新n-1次边,若到第n点更新的边数>=n,那么存在负环
模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
//dist 数组无需初始化,因为我们最终需要求的不是真正的距离,只要存在负环一定会被无线更新
queue<int> q;
// 因为要求所有可能的负环,所以所有点都当做起点加进去
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
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])//每个点不一定只被更新一次,即st[j] == true的点可能被再次更新。
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
- Floyd算法求多源最短路
时间复杂度:O(n*n*n)
原理:
基于动态规划,d[k,i,j]表示从i到j经过1~k这些中间点的最短距离
d[k,i,j]=d[k-1,i,k]+d[k-1,k,j] ,优化为:d[i,j]=d[i,k]+d[k,j]
模板:
注:一定要先循环k,i、j的先后顺序无所谓
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=210,INF=1e9;
int n,m,q;
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,&q);
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--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
d[a][b]=min(d[a][b],c);
}
floyd();
while(q--)
{
int a,b;
scanf("%d%d",&a,&b);
int t=d[a][b];
if(t>INF/2) puts("impossible");
else printf("%d\n",t);
}
return 0;
}