SPFA求单源最短路径
来源:互联网 发布:淘宝不能追加评价 编辑:程序博客网 时间:2024/06/05 20:15
序
求最短路径的算法有很多,各有优劣。
比如Dijkstra(及其堆(STL-priority_queue)优化),但是无法处理负环的情况;
比如O(n^3)的Floyd算法;比如Bellman-Ford算法,可以处理负环的情况。
SPFA算法就是基于Bellman-Ford算法的改进。
SPFA,全称为Shortest Path Faster Algorithm,也被很多Oler笑称为Super Fast Algorithm.
无可否认的是,SPFA的效率的确很高。
逻辑与思路
SPFA的核心代码很短,只有三十行(但是还有各种初始化)。
乍一看就是一个广度优先搜索。下文的代码是一个指针操作的,进行一定优化的,使用一个不太常见的方法存边写的spfa函数。
初始化
1.将所有点的距离设为INF(memset(0x3f)),及无穷大,将到源点的距离设为(dis[st] = 0);
2.将源点压入队列q;(q.push_back(st));
循环执行直到队列为空
取出队首节点cur,对所有与cur相连的节点进行以下操作:
如果源点到cur的距离与cur到该节点距离的和小于源点到该节点的距离,则更新源点对该节点的距离,并将该节点压入队列;
也就是: if(dis[cur] + edge[cur, i] < dis[i]) update(dis[i]); q.push_back(i);
最终得到的各点距离就是最短路径(如果不连通,则距离为初始值INF)。
原理:
从直觉层面上想,这不难理解。
如果该节点的最短路径被更新(也就是变小),则说明通过该节点到其他节点的路径长度便有可能因此变小。
于是压入队列,等待下一次操作。
优化
用一个双端队列维护。
如果得到新的距离dis[ne]小于位于队首的距离dis[q.front()],则将该节点压入队首,反之则压入队尾。实测效率的确更高。
另一个优化是队列节点进出现一次。
用一个数组wh[I]表示节点i是否在队列中,如果在则只更新距离不压入队列(因为队列里有)。
图的存储方式
大致有三种方式存储。
一是邻接矩阵,不推荐,除了好写一点,又费空间又费时间。
二是前向心,存每条弧的next指向与之节点相连的另一节点。不常写,不做评价。
三是将所有弧存入一个vector(或者数组),记录所有节点与之相连弧的编号。下文的代码实现便是基于这种存储方式。
具体存储方式:
1.读入每一条弧的信息,将它们存入vector中。
2.每次存储时,将该弧的标号(或者指针)存到另一个vector中。
例如读入一条弧: edge a -> b, weight(a, b) = c.
存边的结构体存储每条弧的始点,终点与权重(最短路径则不需要存权重)
struct Edge{ int st, en, weight; Edge(){} Edge(int s, int e, int w): st(s), en(e), weight(w){}};
那么按照以下操作。(如果是有向图则不需要存回边)
read(a); read(b); read(c);edge.push_back(Edge(a, b, c));edge.push_back(Edge(b, a, c));arc[a].push_back(edge.size()-2);arc[b].push_back(edge.size()-1);
当然也可选择只存一次边,但是如果是存储网络流,则必须这么存。
那么遍历所有与节点x相连的弧便是这样的。
for(int i = 0, i_end_ = arc[x].size(); i < i_end_; ++i){ int j = arc[x][i]; Edge& e = edge[j]; ne = e.en;//e.en就是弧的终点}
SPFA代码实现
void Spfa(){ int q[maxn]; int *s = q, *t = s + 1, *en = q + n + 1; int cur, ne, cur_dis; bool wh[maxn] = {0}; Edge *j; memset(dis, 0x3f, sizeof dis); dis[st] = 0; *s = st; while(s != t) { cur = *s++; s = s == en ? q : s; wh[cur] = 0; REP(i, 0, arc[cur].size())//for(int i = 0; i < arc[cur].size(); ++i) { j = arc[cur][i]; ne = j -> en; cur_dis = dis[cur] + j -> weight; if(cur_dis < dis[ne]) { dis[ne] = cur_dis; if(wh[ne]) continue; wh[ne] = 1; if(dis[ne] < dis[*s]) { s = s == q ? en - 1 : s - 1; *s = ne; } else *t++ = ne; t = t == en ? q : t; } } } return ;}
以上代码为了提高整体效率,牺牲了一定可读性,基本使用指针操作。
但是效率的提高是非常可观的。从800+ms -> 400+ms。
可以在洛谷上交一下板子题。
luogu P3371 【模板】单源最短路径
完整代码实现
/*About:From: luogu 3371Auther: kongse_qiDate:2017/05/24*/#include <cstdio>#include <cstring>#include <cstdlib>#include <vector>#include <ctype.h>namespace IO{ static int in; static char *X, *Buffer; static char c; void Get_All() { fseek(stdin, 0, SEEK_END); long long file_lenth = ftell(stdin); rewind(stdin); X = Buffer = (char*)malloc(file_lenth); fread(Buffer, 1, file_lenth, stdin); return ; } void Get_Int() { in = 0; while(!isdigit(*X)) ++X; while(isdigit(*X)) { in = in * 10 + *(X++) - '0'; } return ; }}//读入优化,注意必须是文件读入using namespace IO;using namespace std;#define read(x) Get_Int(), x = in;#define REP(i, a, b) for (int i = (a), i##_end_ = b; i < i##_end_; ++i)#define min(a, b) a > b ? b : aconst int maxn = 10005;const int INF = 2147483647;const int maxm = 500005;struct Edge{ int st, en, weight; Edge(){} Edge(int f, int t, int w): st(f), en(t), weight(w){}};int n, m, st;int dis[maxn]; Edge edge[maxm], *cur = edge;vector<Edge*> arc[maxn];int q[maxn]; void Add_Edge(int& st, int& en, int& weight){ *cur = Edge(st, en, weight); arc[st].push_back(cur++); return ;} void Read(){ int a, b, c; read(n); read(m); read(st); REP(i, 0, m) { read(a); read(b); read(c); if(a != b) { Add_Edge(a, b, c); } } return ;}void Spfa(){ int *s = q, *t = s + 1, *en = q + n + 1; int cur, ne, cur_dis; bool wh[maxn] = {0}; Edge *j; memset(dis, 0x3f, sizeof dis); dis[st] = 0; *s = st; while(s != t) { cur = *s++; s = s == en ? q : s; wh[cur] = 0; REP(i, 0, arc[cur].size()) { j = arc[cur][i]; ne = j -> en; cur_dis = dis[cur] + j -> weight; if(cur_dis < dis[ne]) { dis[ne] = cur_dis; if(wh[ne]) continue; wh[ne] = 1; if(dis[ne] < dis[*s]) { s = s == q ? en - 1 : s - 1; *s = ne; } else *t++ = ne; t = t == en ? q : t; } } } return ;}void Print(){ int *p = dis + 1; REP(i, 1, n + 1) { *p = *p == 0x3f3f3f3f ? INF : *p; printf("%d ", *p++); } return ;}int main(){ freopen("test.in", "r", stdin); Get_All(); Read(); Spfa(); Print(); return 0;}
至此结束。
箜瑟_qi 10:07 2017.05.25
- SPFA求单源最短路径
- SPFA算法求单源最短路径
- SPFA:改进的BellmanFord求单源最短路径算法
- SPFA算法求最短路径
- SPFA 单源最短路径
- zoj1655 spfa 最长路径
- spfa最短路径
- 最短路径spfa
- SPFA单源最短路径
- spfa 最短路径
- 最短路径 -- spfa
- 单源最短路径-spfa算法
- SPFA输出路径
- SPFA计算路径
- Spfa单源最短路径算法
- 【模板】Spfa单源最短路径
- SPFA最短路径
- SPFA--最短路径
- MySQL注入工具sqlsus
- 天鸽年营收超8亿,为何战略投资月活1000万+的花椒直播
- 仿华为手机管家的病毒扫描 Android自定义View
- key是索引约束
- eclipse中maven使用详解
- SPFA求单源最短路径
- VLC全部详细参数,libvlc_new函数参数,VLC SDK开发
- windows server2008域管理员密码二种恢复方法
- Jquery 表格操作
- 在web项目中添加文本编辑器 ckeditor
- 屏幕适配之带虚拟按键手机屏幕适配
- 关于iOS时间的传递和响应机制_转载自马在路上大神的博客
- Git .gitignore
- 网站需要测试的五个方面