没有用的话qaq :
Ummmm…图论的大部分知识本来早就有学过,只是一直没有写成博文来梳理,但既然上了qbxt DP图论就写一篇来总结下,主要是来听DP的,但…由于太菜的原因,DP听得天花乱坠QWQ
图中的最短路算法分为单源最短路算法(dijkstra,Bellman-food,spfa)和多源最短路算法(floyd),单源最短路算法是求图上一点到其他任意一点的最短路径,多源最短路算法是求图上任意两点之间的最短路。
一,多源最短路算法
Floyd弗洛伊德算法,运用了动态规划的思想,用邻接矩阵存储,期初将从任何一点到其他所有点的最短路都置成正无穷,然后输入时修改有的权值,其主要思想是动态规划,在最外围枚举点,看看有没有一个点可以更新两点之间的最短路。
代码简单易懂
memset(dis,0x3f,sizeof(dis)); for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
时间复杂度为O(n^3 ),空间复杂度为O(n^2),时间和空间均不是很优,一般较少采用,一般在求n范围较小的多源最短路题中才有出现。
可以适用于有负权边的情况,可以判断图的连通性,可以传递闭包
二,多源最短路算法
1,Dijkstra
a.将所有点分为两个集合,最短路确定的集合S 和最短路未确定的集合T,初始S ={s}
b.求T 中每个点v 的当前最短路dv = min<u,v>∈E,u∈S{du + wu,v}
c.取出T 中dv 最小的点,其最短路一定就是dv,将其加入S
d.不断重复上面的操作,直到所有点的最短路都确定
与普利姆的思想基本相同,代码实现也基本相同,同样朴素写法时间复杂度较劣,可以采用堆优化至O((N + M)logN)
inline void dijsktra(int s) { for(int i=1;i<=n;i++) dist[i]=INF,vis[i]=false; heap.push(make_pair(0,s)); vis[s]=true; while(!heap.empty()) { int p=heap.top().second; heap.pop(); if(vis[p]) continue; vis[p]=true; for(int i=first[p];i;i=edge[i].nxt) { int e=edge[i].ed,d=edge[i].len; if(dist[e]>dist[p]+d) { dist[e]=dist[p]+d; heap.push(make_pair(-dist[e],e)); } } } }
Dijikstra算法是图论最短路算法中最高效的算法,但注意其不适用于含有负权边的情况。
3,Bellmanfood,SPFA
**其实是一个东西啦,SPFA是对Bellfood的优化,所以在这里就只整理SPFA了,比起Dijkstra,SPFA的用途更为广泛,其适用于有负权边的情况,可以判断一个图里是否有负环,但硬伤是效率,极容易被卡。
我们先看Bellmanfood的算法流程:
a.初始令ds = 0,其余di =∞
b. 依次使用每一条边< u, v > 更新,dv min{dv , du + wu,v}
c.不断循环直到所有di 都不会更新
d.因为任何一条最短路包含至多n − 1 条边,没有负环则不超过n 轮就会停止
SPFA考虑使用队列优化Bellman-Ford 算法,如果更新了du,则将u入队。每次取队首u 更新其邻点v 的dv。因为Bellmanfood会遍历一些不可能完成松弛操作的情况。**
代码
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<queue> using namespace std; struct node { int ed,len,nxt; }; node edge[2333]; int n,m,first[2333],dis[2333],cnt; bool vis[2333]; queue <int> q; inline void add_edge(int s,int e,int d) { cnt++; edge[cnt].ed=e; edge[cnt].len=d; edge[cnt].nxt=first[s]; first[s]=cnt; } inline void spfa(int st) { memset(dis,0x3f,sizeof(dis)); memset(vis,false,sizeof(dis)); q.push(st); vis[st]=true; while(!q.empty()) { int p=q.front(); vis[p]=false; for(int i=first[p];i;i=edge[i].nxt) { int e=edge[i].ed; int d=edge[i].len; int newd=dis[p]+d; if(newd<dis[e]) { dis[e]=newd; if(!vis[e]) { q.push(e); vis[e]=true; } } } } return; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int s,e,d; scanf("%d%d%d",&s,&e,&d); add_edge(s,e,d); add_edge(e,s,d); } spfa(1); return 0; }//随手一写,不保证编译能过 Orz QwQ
SPFA判负环,就要有一个点的进队次数大于等于N,那么我们就认为有负环,因为负环会产生最短路为无限小,因此它会在那不停的转,有时候对于比较稠密的图,可能会卡SPFA判负环,所以对于比较稠密的图把队列换成栈可以更快的找出负环。
队列实现代码
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<queue> using namespace std; struct node { int ed,len,nxt; }; node edge[2333]; int n,m,first[2333],inq[2333],dis[2333],cnt; bool vis[2333]; queue <int> q; inline void add_edge(int s,int e,int d) { cnt++; edge[cnt].ed=e; edge[cnt].len=d; edge[cnt].nxt=first[s]; first[s]=cnt; } inline bool spfa(int st) { memset(dis,0x3f,sizeof(dis)); memset(vis,false,sizeof(dis)); q.push(st); vis[st]=true; inq[st]++; while(!q.empty()) { int p=q.front(); vis[p]=false; for(int i=first[p];i;i=edge[i].nxt) { int e=edge[i].ed; int d=edge[i].len; int newd=dis[p]+d; if(newd<dis[e]) { dis[e]=newd; if(!vis[e]) { q.push(e); inq[e]++; vis[e]=true; if(inq[e]>=n) return true; } } } } return false; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int s,e,d; scanf("%d%d%d",&s,&e,&d); add_edge(s,e,d); add_edge(e,s,d); } spfa(1); return 0; }
写在最后,Yousiki Orz Orz