该文章为个人学习笔记,仅供个人学习,大部分内容参考自,若该笔记有说法不严谨或错误之处,欢迎评论区批评指正
该笔记参考学习文章❤️❤️最短路径算法合集❤️❤️
最短路径问题,解决思路及时间复杂度
一.朴素版dijkstra算法
该算法通过找到当前没有确定最短路长度的点当中距离最小的那一个值,用该点来更新后续的点,即可得到最短路径
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, M = 2000010;
int n, m;
int g[N][N], dist[N]; // g[][]存储图的邻接矩阵, dist[]表示每个点到起点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 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])) //当前j不是确定的最短路径,并且当前不是最短的
t = j;
st[t] = true;
for(int j = 1; j <= n; j ++)
{
dist[j] = min(dist[j],dist[t] + g[t][j]);
//用1-t-j的距离更新1-j的值
}
}
if(dist[n] == 0x3f3f3f3f)
return -1; //表示从起点到n号点是不连通的则返回-1
else
return dist[n];
}
int main()
{
cin >> n >> m;
memset(g,0x3f,sizeof g);
//for (int i = 1; i <= n; i++) //先将图的邻接矩阵初始化
// for (int j = 1; j <= n; j++)
// g[i][j] = INF;
while(m --)
{
int a, b, c; //表示存在一条从点a到点b的有向边,边长为c
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);//可能会有两点间多条路线,记录最短的
}
cout << dijkstra();
return 0;
}
二. 堆优化的dijkstra算法
优化版的dijkstra算法,算法思想与朴素版的dijkstra算法相同,但优化版的算法,使用邻接表存储图(稀疏图),并使用优先队列可直接找到当前没有确定最短路长度的点当中距离最小的那一个值
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int N=510;
int h[N],e[N],w[N],ne[N],idx;
int n,m;
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);//初始化距离为0x3f(足够大)
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII> > heap;//小顶堆(队头元素为堆中最小的,自动排序)
heap.push({0,1});
while(heap.size())
{
PII t=();//访问队头元素
heap.pop();//删除队头元素
int a=t.second,b=t.first;//大顶堆pair<int,int>类在比较时先比较first元素,第一个相同再比较second,故将距离放在first位置
if(st[a]) continue;//已确定是最短路径,进入下一层循环
st[a]=true;
for(int i=h[a];i!=-1;i=ne[i])//对该结点对应的单链表进行遍历
{
int j=e[i];此时i代表当前节点的开始位置,j代表节点的结束位置,w[i]值得是路径距离
if(dist[j]>dist[a]+w[i])
{
dist[j]=dist[a]+w[i];///用1-a-j的距离更新1-j的值
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else 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();
cout<<t;
return 0;
}
优先队列 priority_queue
和队列的基本操作相同,只是添加了一个内部排序,它本质上是一个堆实现的
priority_queue q;//默认为大顶堆
//降序队列(大顶堆,队头元素最大)
priority_queue <int,vector,less >q;
//升序队列(小顶堆,队头元素最小)
priority_queue <int,vector,greater > q;
详情可参照c++优先队列
三.Bellman-ford算法
松弛操作的理解
对于边3-2,它可以使3-1变成3-2-1,从而使其距离变短,此过程称为松弛。(松弛点数,拉紧距离)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 505,M = 10010;
int n,m,k;
int dist[N],backup[N];
struct Edge
{
int a,b,w;
}edges[M];//结构体存储a-b边,边权为w
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for(int i = 0; i < k; i ++) //题中最多k条边,即最多做k次松弛
{
memcpy(backup,dist, sizeof dist);//backup数组储存上一次结果的备份,避免发生串联
for(int j = 0; 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; //避免出现路径上存在(0x3f+(负权边)) != 0x3f 的情况
return dist[n];
}
int main()
{
cin >> n >> m >> k;
for(int i = 0; i < m; i ++)
{
int a,b,c;
cin >> a >> b >> c;
edges[i] = {a,b,c};
}
int t = bellman_ford();
if( t == -1) cout << "impossible";
else cout << t;
return 0;
}
四.SPFA算法
对bellman-ford算法的优化,由于在bellman-ford算法中dist[b] = min(dist[b],dist[a] + w);
只有dist[a]的值变小才会改变dist[b]的值,那我们基于这一点对算法进行优化。
我们定义一个队列,用队列存储改变的dist[a],然后遍历更新以a为起点的路径的终点可能会变小,
如果更新成功即dist[b] < dist[a] + w
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=100005;
int dist[N];
int n,m;
int e[N],ne[N],w[N],h[N],idx;
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;
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);//存储改变的dist[a]的值
st[j]=true;
}
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
int k=spfa();
if(k==-1) cout<<"impossible";
else cout<<k;
return 0;
}
Spfa算法还可以用来判断负环
五.Floyd算法
标准弗洛伊德算法,三重循环。
需要注意循环顺序不能变:第一层枚举中间点,第二层和第三层枚举起点和终点。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=210,INF=0x3f3f3f3f;
int dist[N][N];
int n,m,k;
int floyd()
{
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
dist[i][j]=min(dist[i][j],dist[i][k]+dist[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) dist[i][j]=0;
else dist[i][j]=INF;
}
}
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
dist[x][y]=min(z,dist[x][y]);
}
floyd();
while(k--)
{
int a,b;
cin>>a>>b;
if(dist[a][b]>INF/2) cout<<"impossible"<<endl;
else cout<<dist[a][b]<<endl;
}
return 0;
}