博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最短路再放送
阅读量:4960 次
发布时间:2019-06-12

本文共 5897 字,大约阅读时间需要 19 分钟。

前言

当我做另一道题使用最短路算法时,我习惯性地去\(Luogu\)博找\(Dijksttra\)的板子,这时候我觉得这种算法应该记住,于是我又重温了一遍最短路算法,发现了很多困惑,尤其是关于\(SPFA\)\(Dijkstra\)算法的区别。因为我的\(SPFA\)\(Dijkstra\)都使用了堆优化。

然后我困惑了很久,终于明白了,准备推样例理解一下,又心血来潮重温了一下\(Dijkstra\)算法为什么不能处理负权边,然后构造了一个带负权的有向图,结果呢。
我的\(Dijkstra\)竟然跑出了正确的答案!
我惊了,拿朋友的板子发现正确的\(Dijkstra\)算法是不能跑负权边的,于是我再去看我的板子,怀疑我的\(Dijkstra\)写成了\(SPFA\),要是我发现了能处理负权边的\(Dijkstra\)算法,我估计会被保送吧哈哈。于是改掉了我的板子,所幸在\(NOIP\)前发现,在此简单总结一下最短路算法。

\(Floyed\)

多源最短路算法,运用了松弛的思想,有很浓重的\(DP\)气息(因为它就是\(DP\)),通过枚举中间点转移。

复杂度\(O(n^3)\),在解决最短路问题上基本没有用处,如果解决多源问题完全可以跑\(n\)\(Dijkstra\),和矩阵乘法结合有一些用,具体我也忘了。

memset(f,inf,sizeof(f));for(int i=1;i<=n;++i) f[i][i]=0;for(int i=1;i<=n;++i)    for(int j=1;j<=n;++j)        for(int k=1;k<=n;++k)          f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

\(SPFA\)

\(SPFA\)是个随机数据下比较快的算法,它的思想是每次从队列中找到一个点,松弛它所连的边,然后出队,注意这个点是可以再次被更新的,也就是可以再次入队重新松弛,不断逼近,直到最后求出最优解。所以卡\(SPFA\)的办法就是让\(SPFA\)跑到终点,然后再从起点更新,让它不停地重复跑就可以了,具体方法是构造一个网格图,将横向边边权设为很小,纵边很大即可。

\(SPFA\)最优复杂度为\(O(KE)\),其中\(K\)是一个常数,但是最坏情况可以达到\(O(VE)\)
众所周知,现在很多最短路题目出题人都会卡\(SPFA\),用双端队列优化或者是堆优化本质都是不变的,都会被卡,所以还是用\(Dijkstra\)吧。
常规的\(SPFA\)

#include
#define N 10005#define maxn 500005#define INF 2147483647using namespace std;int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;bool vis[N];struct Edge{ int next,w,v;}e[maxn];inline void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; return;}void spfa(int s){ for(int i=1;i<=n;i++){ dis[i]=INF; vis[i]=true; } dis[s]=0; queue
q; q.push(s); vis[s]=false; while(!q.empty()){ eu=q.front(); q.pop(); vis[eu]=true; for(int i=head[eu];i;i=e[i].next){ ev=e[i].v; if(dis[eu]+e[i].w
'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f;}int main(){ cin>>n>>m>>s; for(register int i=1;i<=m;i++) { cin>>x>>y>>z; add(x,y,z); } spfa(s); for(register int i=1;i<=n;i++) printf("%d ",dis[i]);}

比较常用的双端队列优化(\(SLF\)优化),大概思想是队列为空或者当前最短距离比队首小时放到队首,否则放到队首,最好再熟悉一下\(STL\)

#include
#define N 100005#define maxn 200005#define INF 2147483647using namespace std;int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;bool vis[N];struct Edge{ int next,w,v;}e[maxn];inline void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; return;}void spfa(int s){ for(int i=1;i<=n;i++){ dis[i]=INF; } dis[s]=0; deque
q; q.push_back(s); vis[s]=1; while(!q.empty()){ eu=q.front(); q.pop_front(); vis[eu]=0; for(int i=head[eu];i;i=e[i].next){ ev=e[i].v; if(dis[eu]+e[i].w
dis[q.back()]) {// int fr = q.front() ; q.pop_front() ;// int ba = q.back() ; q.pop_back() ;// q.push_front(ba), q.push_back(fr) ;// } if(q.empty()||dis[ev]
'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f;}int main(){ cin>>n>>m>>s; for(register int i=1;i<=m;i++) { x = read(), y = read(), z = read() ; add(x,y,z); } spfa(s); for(register int i=1;i<=n;i++) printf("%d ",dis[i]);}

\(SPFA\)堆优化:算是比较快的,但是不常用,这个可以过掉洛谷的单源最短路(标准版),而且比\(Dijkstra\)要快

#include
#define N 200000 + 10#define maxn 200000 + 10#define INF 2147483647using namespace std;int dis[N],cnt,n,m,eu,ev,head[N],x,y,z,s;bool vis[N];struct Edge{ int next,w,v;}e[maxn];inline void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; return;}struct cmp{ bool operator ()(int &x, int &y) { return dis[x] > dis[y]; }};void spfa(int s){ for(int i = 1; i <= n; i++) dis[i] = INF; dis[s]=0; priority_queue
, cmp> q; q.push(s); vis[s]=1; while(!q.empty()){ eu=q.top(); q.pop(); vis[eu]=0; for(int i=head[eu];i;i=e[i].next){ ev=e[i].v; if(dis[eu]+e[i].w

\(Dijkstra\)

\(Dijkstra\)是最常用的最短路算法,原始时间复杂度为\(O(n^2)\),堆优化后时间复杂度为\(O(nlogn)\),而且比较稳定,很难卡掉

他的思想是从已更新的点集中选出最近的点,默认这个距离为该点的最短距离,再用它来更新其它点,这个过程可以用堆优化。
“默认这个距离为该点的最短距离”这是一个贪心的思想,那么这个贪心为什么是对的呢。
尝试用反证法证明,假设\(S-->A\)不是到\(A\)的最短路,而是通过集合外的一个点作为中间节点,即\(S-->B-->A\),因为我们选择的是最近的点,所以\(S-->A\)一定小于\(S-->B\),而且\(B-->A>0\)所以\(S-->A\)\(S-->B-->A\)更优,这和假设矛盾,得证贪心成立

#include
#include
#include
#include
#define re register#define maxn 200010using namespace std;int head[maxn],vis[maxn],s,cnt,dis[maxn],n,m,a,b,c;struct Edge{ int v,w,nxt;}e[maxn<<2];inline void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt;}struct node{ int u,d; bool operator <(const node&rhs) const{ return rhs.d
q; q.push((node){s,0}); //vis[s]=1; while(!q.empty()) { node f=q.top(); q.pop(); int now=f.u,dd=f.d; if(vis[now]) continue; vis[now]=1; for(int i=head[now];i;i=e[i].nxt) { int ev=e[i].v; if(dis[ev]>dis[now]+e[i].w) { dis[ev]=dis[now]+e[i].w; if(!vis[ev]) { q.push(node{ev,dis[ev]}); } } } }}int main(){ scanf("%d%d%d",&n,&m,&s); for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff; for(re int i=1;i<=m;++i) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); } dijkstra(); for(re int i=1;i<=n;++i) printf("%d ",dis[i]); return 0;}

这里补一下一直困惑的结构体重组操作:

结构体重载就相当于\(cmp\)函数进行排序

bool operator <(const node&rhs) const{        return rhs.d

标准格式如上,上句话的意思是:一个结构体比另一个结构体小当且仅当这个结构体的\(d\)大于另一个结构体的\(d\)\(rhs\)相当于别的结构体

又因为堆默认大根堆,从大到小排序,那么反过来\(d\)就从小到大排序了。

转载于:https://www.cnblogs.com/Liuz8848/p/11296234.html

你可能感兴趣的文章
【原】iOS中KVC和KVO的区别
查看>>
OMAPL138学习----DSPLINK DEMO解析之SCALE
查看>>
IoC的基本概念
查看>>
restframework CBV试图的4种方式
查看>>
大图居中,以1920px为例
查看>>
Python3 图片转字符画
查看>>
[C陷阱和缺陷] 第7章 可移植性缺陷
查看>>
人需要治愈
查看>>
linux中configure文件默认执行结果所在位置
查看>>
Windows向Linux上传文件夹
查看>>
20180104-高级特性-Slice
查看>>
6个SQL Server 2005性能优化工具介绍
查看>>
nginx启动、关闭命令、重启nginx报错open() "/var/run/nginx/nginx.pid" failed
查看>>
BZOJ 3097 Hash Killer I
查看>>
UINavigationController的视图层理关系
查看>>
html阴影效果怎么做,css 内阴影怎么做
查看>>
宏观经济
查看>>
综合练习:词频统计
查看>>
BZOJ1026: [SCOI2009]windy数
查看>>
样板操作数
查看>>