最短路问题大致分为这几个情景和算法,主要就是单源或者多源,以及是否存在负权边。今天刚刚学完,来总结一下。
1.Dijkstra算法
#include <stdio.h>
#include <string.h>
# define N 510
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int min(int a,int b)
{
return a<b?a:b;
}
int dijkstra()
{
memset(st,0,sizeof st);//初始化访问数组 记录是否访问过
memset(dist,0x3f,sizeof dist);//dist[i]为起点到i点的距离 初始化为正无穷
dist[1]=0;//初始化起点距离为0
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]);
}
return dist[n];
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF&&(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);//若两个点有多条路 保留距离最短的路
//若题目给的无向边
则g[a][b]=g[b][a]min(g[a][b],c);
}
int t=dijkstra();
printf("%d\n",t);
}
return 0;
}
2.Bellman-Ford算法(有边数限制的最短路)
若边中,有权重为负数或题目中要求“请你求出从1号点到n号点的最多经过k条边的最短距离”之类的限定经过边数的语言,则应使用此算法。
存储数据时用结构体较方便。
#include <cstring>
#include <iostream>
#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 last[N];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);//初始化
dist[1] = 0;
for (int i = 0; i < k; i ++ )
{
memcpy(last, dist, sizeof dist);//备份数据
for (int j = 0; j < m; j ++ )
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);//由于题目限制了经过k条边 固为了防止串连 需要用备份数据进行松弛操作
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");//因为存在负边,故更新时可能会小于正无穷
else printf("%d\n", dist[n]);
return 0;
}
3.Spfa算法求最短路
Spfa算法单源最短路径的一种算法,它是Bellman-ford的队列优化
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。
但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。
实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
此外,SPFA算法还可以判断图中是否有负权环,即一个点入队次数超过N。
————————————————
以上内容摘自CSDN博主「算法之心」
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;//链表存储数据m
int dist[N];
bool st[N];
void add(int a,int b,int c)//在a,b点之间插入长度为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;
for(int i=h[t];i!=-1;i=ne[i])//从t点向与他相邻的节点遍历
{
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()
{
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;
}
4.Spfa 判断负环
通过spfa判断是否有负环可以通过某两个点的最短距离之间需要经过的边数k是否大于等于点的数量n,根据抽屉原理,若设1-x点之间经历了n条边,则1-x路径上至少经历了n+1个点,一共只有n个点,故一定有两个点是相同的,故存在负环。
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
const int N=10010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N],cnt[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()
{
queue<int> q;
for (int i = 1; i <= n; i ++ )//因为负环不一定经过点1 故初始化使全部点入队
{
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])
{
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())
printf("Yes\n");
else
printf("No\n");
return 0;
}
5.Floyd算法(多源最短路)
Floyd算法也叫插点法,实则利用动态规划思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra相似,不过是多源,储存数据用邻接矩阵储存。
#include <cstring>
#include <iostream>
#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;
}
大概总结一下,堆优化的dij随后会补上。