最短路算法

本篇文章将介绍一些关于最短路的基本算法.

Floyd算法

处理全源最短路问题,时间复杂度较高,容易实现,时间复杂度 O(N3) .

//n表示节点总数,f[i][j]表示i到j的最短路径
for(int k=1;k<=n;k++){//表示仅允许经过节点1-k
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);//比较经过k点与不经过k点的最短路
}
}
}

Bellman_ford算法

基于松弛原理的算法,可以带负边权.
松弛原理即对于边 (u,v) ,有 dis[v]=min(dis[v],dis[u]+w[u,v]) ,即我们判断将经过v与不经过v的路径进行比较,取到更优的路径,更新为最优的路径.
设这个图有 m 条边与 n 个节点,因为每次松弛操作要对每一条边都进行松弛操作,进行 m 次,最短路最多有 n1 条边,最多进行 n1 次松弛,所以此算法的时间复杂度为 O(nm) .
因为此算法时间复杂度与队列优化后的SPFA算法的最坏时间复杂度相同,所以可以优先使用SPFA算法的代码,Bellman_ford算法代码实现略.
此算法可以判断负环,如果第 n 次松弛仍能找到可以松弛的边,则此图有负环.(此做法只能判断由源点出发能否到达负环,更为严谨的做法是建立一个超级源点,将此点与所有点连接一条权值为 0 的边).

SPFA算法

Bellman_ford算法的队列优化,我们发现不是每个点都需要松弛,而是被松弛后的点所连接的边才可能引起下一次松弛,我们用队列记录可能被松弛的点.
SPFA算法同样也可以判断负环,需要记录最短路经过多少边,如果最短路经过 n 条边,则说明此图有负环.
此算法的最坏时间复杂度为 O(nm) ,可以制作hack数据来卡掉此算法,所以在没有负边权时尽量使用Dijkstra算法.但若题目中有负边权且无特殊条件下,SPFA算法作为正解不应被hack.

struct edge{
int v,w;
}g[N];
int dis[N],vis[N],cnt[N];
queue<int>q;
bool spfa(int s,int n){
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(s);
vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(auto x:g[u]){
int v=x.v;w=x.w;
if(dis[v]>dis[u]+w){//松弛操作
dis[v]=dis[u]+w;
cnt[v]=cnt[u]+1;//记录最短路边数
if(cnt[v]>=n) return false;//判断负环
if(!vis[v]) q.push(v),vis[v]=1;
}
}
}
return true;
}

Dijkstra算法

同样要用到松弛原理,只能在非负边权图中使用.
考虑一种贪心,我们定义集合 p 为已确定最短路的点集,集合 q 为未确定最短路的点集,一点dis值为起点到此点的距离,我们先将起点的dis值设为 0 ,其他都是正无穷,我们在集合 q 中找到dis值最短的点,将此点加入集合 p ,对此点进行松弛操作,直到所有点都被加入集合 p .
我们可以证明当前贪心策略正确,因为全局的边权都为非负数,全局最小值无法被其他节点更新,所以当我们找到dis值最小的点时,他已经是起点到此点的最短路径,我们不断用全局最小值进行拓展,最终可以得到所有点的最短路径.
在稠密图时可直接运用暴力做法做最优,时间复杂度为 O(n2) ,稀疏图是则需要使用优先队列优化,时间复杂度为 O(mlogm)
下面仅给出实现难度略大的优先队列优化做法.

struct node{
int u,v,w;
};
vector<node> g[N];
int vis[N],dis[N];
struct T{
int d,u;
friend bool operator<(T a,T b){
//重构运算符<(重构>会编译错误),因为优先队列默认从大到小排序,相反重构可以让他从小到大排序
return a.d>b.d;
}
};
priority_queue< T > q;
void dijkstra(int start){
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[start]=0;//初始化起点的dis值为0
q.push((T){0,start});//
while(!q.empty()){
T t=q.top();
q.pop();//取出当前路径最短的点
int u=t.u;
if(vis[u]) continue;//如果该点已经找到最短路径则无须在继续
vis[u]=1;//标记此点已经找到最短路径
for(auto e:g[u]){
int v=e.v,u=e.u;
if(dis[v]>e.w+dis[u]){//松弛操作
dis[v]=e.w+dis[u];
q.push((T){dis[v],v});//将得到路径长度的点放入优先队列
}
}
}
}
posted @   BSS梅者如西  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
   
点击右上角即可分享
微信分享提示