单源最短路(最短路的起始点都是1)
所有边权为正
1.朴素dijkstra:
时间复杂度:O(n^2)
应用场景:稠密图
特点:使用邻接矩阵存图
注意:无向图需记录两次
·设dis[1]=0,其他dis[i]=INF;
·循环n次{
在所有未标号的节点中选出dis[x]最小的节点x
对节点x标记
对于x出发的所有边(x,y),更新d[y]=min( , )
·}
·已经标记过的点再也不会遍历
代码略
2.堆优化版dijkstra
时间复杂度:O(mlogn) m指边数,n指点数
应用场景:稀疏图
特点:使用邻接表存图
·设dis[1]=0,其他dis[i]=INF;
·创一个优先队列(务必注意点数在前or距离在前)
priority_queue<PII, vector<PII>, greater<PII>> q;
·将更新过的点入队,已经确定的点出队
代码略
存在负权边
1.Bellman_ford算法
时间复杂度:O(mn) m指边数,n指点数
特点:
1.可以解决有边数限制的最短路
2.可以判断是否存在负环,若有,则n-1次更新后还能更新
for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)注意:back[] 数组是上一次迭代后 dist[] 数组的备份,由于是每个点同时向外出发,因此需要对 dist[] 数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点。
return-1的判断条件写的是dist[n]>0x3f3f3f3f/2
代码略
2.SPFA
时间复杂度:一般O(m),最坏O(mn) m指边数,n指点数
特点:对bellman的优化,避免遍历所有边。可以有负权边,但不可以存在负环!!
使用队列(没有优先,类似bfs),只是把发生改变的点入队罢了,且状态 st 数组可以从true变为false。
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N];//各点到源点的距离
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int spfa(){
queue<PII> q;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
q.push({0,1});
st[1]=true;
while(q.size()){
PII p=q.front();
q.pop();
int t=p.se;
st[t]=false;//从队列中取出来之后该节点st被标记为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]){//当前已经加入队列的结点,无需再次加入队列,
//即便发生了更新也只用更新数值即可,重复添加降低效率
st[j]=true;
q.push({dist[j],j});
}
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else 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 res=spfa();
if(res==-1) puts("impossible");
//注意可以最短路长度为-1
else printf("%d",res);
return 0;
}
作者:orzorz
链接:https://www.acwing.com/solution/content/9306/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
多源汇最短路(最短路起始点随机)
Floyd算法
时间复杂度:O(n^3)
特点:对是否有负环,负权边没限制。比较简单。
引入第k个点进行松弛
#include <iostream>
using namespace std;
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
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() {
cin >> 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--) {
cin >> x >> y >> z;
d[x][y] = min(d[x][y], z);
//注意保存最小的边
}
floyd();
while(k--) {
cin >> x >> y;
if(d[x][y] > INF/2) puts("impossible");
//由于有负权边存在所以约大过INF/2也很合理
else cout << d[x][y] << endl;
}
return 0;
}