前言

如题,这里讲解四个最短路,Floyd,dijkstra+堆优化,Bellman-ford,SPFA。(后面两个不是一样的嘛...)

进入正题吧。


Floyd:求最短路 / 判断两点是否相连

这是所有最短路算法里面最强大的最好写的也是唯一一个多源最短路算法,本质是DP。

核心思想是对于每两个点枚举第三个点,中转点,判断如果把路径改为经过这个点走是否变短,更新即可。

无法走到相当于无限长。

复杂度O(n3),因为要用到三重循环。一般要求i,j,k不相等。

int dis[][];
//dis[i][j]表示从i点到j点的最短距离
void ini(){memset(dis,INF,sizeof(dis));for(int i=1;i<=n;++i)dis[i][i]=0;for(...){scanf("%d%d%d",&x,&y,&w);dis[x][y]=dis[y][x]=w; //无向图//dis[x][y]=w;有向图
    }
}
void floyd(){  for(int k=1;k<=n;++k)for(int i=1;i<=n;++i)for(int j=1;j<i;++j)if(dis[i][j]>dis[i][k]+dis[k][j])dis[i][j]=dis[j][i]=dis[i][k]+dis[k][j];
}

这里放一道题,利用了floyd的本质思想。

luoguP1119灾后重建

按时间顺序floyd。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
int x,y,w,n,m,Q,dis[203][203],t[203],nowfor=0,askt;
int main(){memset(dis,INF,sizeof(dis));for(int i=0;i<n;++i)dis[i][i]=0;scanf("%d%d",&n,&m);for(int i=0;i<n;++i)scanf("%d",&t[i]);for(int i=1;i<=m;++i){scanf("%d%d%d",&x,&y,&w);dis[x][y]=dis[y][x]=w;}scanf("%d",&Q);while(Q--){scanf("%d%d%d",&x,&y,&askt);if(t[x]>askt||t[y]>askt){printf("-1\n");continue;}while(t[nowfor]<=askt&&nowfor<n){for(int i=0;i<n;++i)for(int j=0;j<n;++j)dis[i][j]=min(dis[i][j],dis[i][nowfor]+dis[nowfor][j]);++nowfor;}/*可以只用新节点来更新,因为更新新路径的时候他其实不是中转点*/if(dis[x][y]!=INF)printf("%d\n",dis[x][y]);else printf("-1\n");}return 0;
}


Dijkstra : 不能处理负权图

朴素:O(N2
堆优化...(据说)手写二叉堆是O(elogv),stl优先队列是O(eloge),斐波那契堆是O(vlogv+e),配对堆复杂度玄学

适用于稠密图(侧重于对点的处理)。

Dij的核心思想是从一个点出发,每次更新行走代价最小的那个点,也就是最近的那个点。注意已经在队列里的点还是会再走的,但出队的不会。(有点像Prim)

用vis数组维护每个点是否出队过(是否为蓝点),已经出队的不再入队。每次取出一个点进行松弛。

朴素:穷举找最小值

堆优化:用优先队列维护最小代价。不能处理负环。运用蓝白点思想。

代码看例题,模版,堆优化。

luoguP4779单源最短路径

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e6+3;
struct Edge{int v,w,next;
}edge[N];
struct CMP{int dis,node;friend bool operator < (CMP a,CMP b){return a.dis>b.dis;}
};
int head[N],tot=0,dis[N],n,m,s;
bool vis[N]; //是否已经走过
void Add_edge(int u,int v,int w){edge[++tot]=(Edge){v,w,head[u]};head[u]=tot;
}
void dijk(){memset(dis,0x3f3f3f3f,sizeof(dis));priority_queue<CMP>Q;Q.push((CMP){0,s});dis[s]=0;while(!Q.empty()){int u=Q.top().node;Q.pop();if(vis[u])continue;vis[u]=true;for(int i=head[u];i;i=edge[i].next){int v=edge[i].v,w=edge[i].w;if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;if(!vis[v])Q.push((CMP){dis[v],v});}}}
}
int main(){scanf("%d%d%d",&n,&m,&s);int u,v,w;while(m--){scanf("%d%d%d",&u,&v,&w);Add_edge(u,v,w);}dijk();for(int i=1;i<=n;++i)printf("%d ",dis[i]);return 0;
}


Bellman-Ford : 不能处理负权回路

适用于稀疏图。

O(VE)的可怕复杂度....

每次穷举每条边看能否更新新的点的距离,直到结束。

好简略....

可以队列优化,其实就是SPFA,详见下一节。


SPFA : 不能处理负权回路  花里胡哨的优化一大堆

关于SPFA,他已经死了,不讲了

据说只要一个菊花图就能卡掉SPFA,总之很容易被卡。但是有个随机SPFA

复杂度上限O(NM) = O(VE)同朴素Bellman

一般复杂度为O(kE)(k为常数,均值为2) 但据说是错的。

适用于稀疏图。

其实我觉得SPFA和Dijkstra很像....

但不用优先队列,普通队列即可。

用vis维护一个点是否在队列中,每次从队首取出一个点进行松弛。

如果新的点不在队列中就加入队列。

优化方法:

1.循环队列(可以降低队列大小)

2.SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j) < dist(i),则将j插入队首,否则插入队尾。(queue做不到系列)

3.LLL:Large Label Last 策略,对每个要出队的元素u,比较dis[u]和队列中dis的平均值,如果dis[u]更大,那么将它弹出放到队尾,取队首元素在进行重复判断,直至存在dis[x]小于平均值。

例题 luoguP3371单源最短路径

queue 普通的队列优化(455ms)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+3,INF=0x7fffffff;
struct Edge{int v,w,nt;
}e[N];
int n,m,s,dis[N],tot=0,head[N];
bool vis[N];
void Add(int u,int v,int w){e[++tot]=(Edge){v,w,head[u]};head[u]=tot;
}
void SPFA(){for(int i=1;i<=n;++i) dis[i]=INF;dis[s]=0;queue<int>Q;Q.push(s);vis[s]=true;while(!Q.empty()){int u=Q.front();Q.pop();vis[u]=false;for(int i=head[u];i;i=e[i].nt){int v=e[i].v,w=e[i].w;if(dis[u]+w<dis[v]){dis[v]=w+dis[u];if(!vis[v]){Q.push(v);vis[v]=true;}}}}
}
int main(){scanf("%d%d%d",&n,&m,&s);while(m--){int u,v,w;scanf("%d%d%d",&u,&v,&w);Add(u,v,w);}SPFA();for(int i=1;i<=n;++i)printf("%d ",dis[i]);return 0;
}

deque双端队列优化(SLF)(452ms)

反正STL信不得(那去SAO啊艹)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+3,INF=0x7fffffff;
struct Edge{int v,w,nt;
}e[N];
int n,m,s,dis[N],tot=0,head[N];
bool vis[N];
void Add(int u,int v,int w){e[++tot]=(Edge){v,w,head[u]};head[u]=tot;
}
void SPFA(){for(int i=1;i<=n;++i) dis[i]=INF;dis[s]=0;deque<int>Q;Q.push_front(s);vis[s]=true;while(!Q.empty()){int u=Q.front();Q.pop_front();vis[u]=false;for(int i=head[u];i;i=e[i].nt){int v=e[i].v,w=e[i].w;if(dis[u]+w<dis[v]){dis[v]=w+dis[u];if(!vis[v]){if(!Q.empty()&&dis[v]<dis[Q.front()])Q.push_front(v);else Q.push_back(v);vis[v]=true;}}}}
}
int main(){scanf("%d%d%d",&n,&m,&s);while(m--){int u,v,w;scanf("%d%d%d",&u,&v,&w);Add(u,v,w);}SPFA();for(int i=1;i<=n;++i)printf("%d ",dis[i]);return 0;
}

SLF+LLL优化(440ms)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+3,INF=0x7fffffff;
struct Edge{int v,w,nt;
}e[N];
int n,m,s,dis[N],tot=0,head[N],SUM=0;
bool vis[N];
void Add(int u,int v,int w){e[++tot]=(Edge){v,w,head[u]};head[u]=tot;
}
void SPFA(){#define X ((double)SUM/(Q.size()+1))//出队了一个for(int i=1;i<=n;++i) dis[i]=INF;dis[s]=0;deque<int>Q;Q.push_front(s);vis[s]=true;while(!Q.empty()){int u=Q.front();Q.pop_front();if(dis[u]>X){Q.push_back(u);continue;}vis[u]=false;for(int i=head[u];i;i=e[i].nt){int v=e[i].v,w=e[i].w;if(dis[u]+w<dis[v]){dis[v]=w+dis[u];if(!vis[v]){if(!Q.empty()&&dis[v]<dis[Q.front()])Q.push_front(v);else Q.push_back(v);SUM+=dis[v];vis[v]=true;}}}}
}
int main(){scanf("%d%d%d",&n,&m,&s);while(m--){int u,v,w;scanf("%d%d%d",&u,&v,&w);Add(u,v,w);}SPFA();for(int i=1;i<=n;++i)printf("%d ",dis[i]);return 0;
}

至于循环队列,请手写队列。

虽然好像优化不大...(其实几十ms完全没用....多测几次总有快有慢,评测误差)。

在大数据的时候还是有用的,而且对于LLL优化,手写队列更占优势,毕竟让deque多那么多push和pop可不是什么好事。


结论

  1. Floyed-Warshall算法,只有数据规模较小且时空复杂度都允许时才可以使用。
  2. Dijkstra算法,不能处理存在负边权的情况,侧重对点的处理,适用于:稠密图。
  3. Bellman-Ford算法,可以求出存在负边权情况下的最短路径,但无法解决存在负权回路的情况,侧重于对边的处理适用于:稀疏图。
  4. SPFA算法,侧重于对边的处理,适用于:稀疏图。

最后的话

  如不能有效判断一个图是否稠密,建议使用堆优化dijkstra而不是SPFA,SPFA的时间复杂度不稳定且非常玄学。

转载于:https://www.cnblogs.com/lsy263/p/11400211.html

最短路四连:致逝去的SPFA相关推荐

  1. 最短路算法详解(Dijkstra/SPFA/Floyd)

    转自:http://blog.csdn.net/murmured/article/details/19281031 一.Dijkstra Dijkstra单源最短路算法,即计算从起点出发到每个点的最短 ...

  2. NFC毕业纪念卡:小小车票,致逝去青春

    时间一晃真快呀,转眼间四年时间如白驹过隙,匆匆而逝.以前总觉得时间很慢,轰轰烈烈的恋爱.说走就走的旅行.--,那些想做的事情等一等总是会做的.现在想来,青春,可能本身就是充满着遗憾吧! 思踌良久,在师 ...

  3. 致逝去的大学四年和工作一年后的自己

      已经很久没有写过博客了,距离上次写博客已经是四个月前了.这四个月,找工作.租房子.独立在陌生的城市生活,然后在工作之余写毕业论文,最后成功毕业.所以在我写这篇博客的时候,就意味着我已经离开学校这座 ...

  4. 最短路合集(Dijkstra、SPFA、Floyd以及路径还原模板)

    目录 一.Dijkstra算法(不能处理存在负权的清况) 1.堆(优先队列)优化版本:(慢,占用内存还大) 2.普通线段树优化版本(一般块) 2.大佬的特殊线段树优化版本:(超快的) 二.SPFA 算 ...

  5. 我写代码的这十年——致逝去的青春

    序言:经常在想,当工作10年.15年.20年时,我会是什么样子的?那些我孜孜以求.乐此不疲的东西,我是否应该放弃一些,抽出时间去追求更高的待遇.更好的生活?还是遵循自己的内心哪怕碰南墙也不回头?程序和 ...

  6. 单源(多源)最短路算法Dijkstra、Bellman-Ford、SPFA

    最短路算法 单源最短路:即一个点到任意点的最短路径 多源最短路:即任意一点到任意一点的最短路径 Dijkstra算法: 这个算法是通过点去更新最短路,每次找离源点最近的一个顶点,然后以该顶点为中心进行 ...

  7. HDU2544 最短路(模版题dijkstra/floyd/spfa)

    Problem Description 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要 ...

  8. 经历 成长——致逝去的时光

    引言 现在真的是感觉到时间的珍贵,转眼间我们十期的师哥师姐就开始毕业了,当他们走了以后我们在提高班就是 老大了,需要扛起提高班的大旗,在自己的时光轴中自己还很小,需要他们的关心然而转眼间他们就开始进入 ...

  9. 【回忆杀】2012年拥有第一台电脑【致逝去的青春】

    高中说起 在2012年的时候吧,高考过后,那个时候一门心思的想当一名体育老师[现在居然还有这个想法,哈哈],最后没有考上自己希望的大学 我记得好像是2012年7月的时候就去重庆投靠朋友,他教我做模具, ...

最新文章

  1. volatile 和 mutable 关键字
  2. 阿里开源分布式事务解决方案 Fescar 全解析
  3. 【0ms优化】剑指 Offer 18. 删除链表的节点
  4. Matlab归一化函数(mapminmax)
  5. Django - 模板相关
  6. cbrt c语音_C语言有哪些鲜为人知的特性?
  7. v8引擎和v12引擎_v8和v12发动机的区别
  8. FCS省选模拟赛 Day7
  9. 同一程序在不同版本的framework下控件中英文显示的问题
  10. 杭电acm阶段之理工大版
  11. 自定义View实现2048
  12. 创始人之间应该如何量化分配股权?
  13. mysql查询历史时刻数据_跨平台实时数据库查询历史数据的方法介绍
  14. 2021-10-14 谷歌浏览器更改默认搜索引擎
  15. Qt之九宫格图片处理
  16. JavaScript中Set的使用
  17. 交换机配置软件具有的作用
  18. 一分钟了解自动化测试 1
  19. zynq7000 资源介绍
  20. 【300+精选大厂面试题持续分享】大数据运维尖刀面试题专栏(八)

热门文章

  1. segmentation fault (SIGSEGV) 定位方法
  2. c语言设置方块颜色,对在c语言中经典俄罗斯方块游戏中怎么改变下落方块颜色...
  3. hackthebox- kotarak(考点:信息搜集隐藏端口 tom 上传 域文件解析 wget-gnu 1.16提权 )
  4. 浅谈Manacher算法与扩展KMP之间的联系
  5. 用CSS3标注引用的出处和来源的巧妙方法
  6. Win系统 - 找回你的 Win8 / Win10 开始菜单
  7. Apache Maven项目提供的EAR插件详解
  8. 纯css实现的流星雨的效果
  9. 学习C++的笔记(算法与数据结构要求(2)(我不是黑瞎子掰棒子
  10. 串口通讯的单工、半双工和全双工的定义、区别及应用