最短路径的算法有很多,各有优劣。

比如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 3371
Auther: kongse_qi
Date: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 : a const 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求单源最短路径的更多相关文章

  1. 51nod 1445 变色DNA ( Bellman-Ford算法求单源最短路径)

    1445 变色DNA 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有一只特别的狼,它在每个夜晚会进行变色,研究发现它可以变成N种颜色之一,将这些颜色标号为0,1 ...

  2. SPFA解决单源最短路径

    SPFA(Shortest Path Faster Algorithm): 一:基本算法 在求解单源最短路径的时候,最经典的是 Dijkstra 算法,但是这个算法对于含有负权的图就无能为力了,而 B ...

  3. SPFA算法与dijkstra算法求单源最短路径的比较

    SPFA是运用队列,把所有的点遍历到没有能更新的,点可以重复入队 如题http://www.cnblogs.com/Annetree/p/5682306.html dijkstra是每次找出离源点最近 ...

  4. Poj 1860 Currency Exchange(Bellman-Ford,SPFA解单源最短路径问题)

    一.题意 有多个货币交易点,每个只能互换两种货币,兑换的汇率不同,并收取相应的手续费.有N种货币,假定你拥有第S中,数量为V,有M个兑换点.问你能不能通过兑换操作使你最后拥有的S币比起始的时候多. 二 ...

  5. Dijkstra算法求单源最短路径

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

  6. AOJ GRL_1_A: Single Source Shortest Path (Dijktra算法求单源最短路径,邻接表)

    题目链接:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_A Single Source Shortest Path In ...

  7. [数据结构与算法-15]单源最短路径(Dijkstra+SPFA)

    单源最短路径 问题描述 分别求出从起点到其他所有点的最短路径,这次主要介绍两种算法,Dijkstra和SPFA.若无负权优先Dijkstra算法,存在负权选择SPFA算法. Dijkstra算法 非负 ...

  8. AOJ GRL_1_B: Shortest Path - Single Source Shortest Path (Negative Edges) (Bellman-Frod算法求负圈和单源最短路径)

    题目链接: http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_B   Single Source Shortest Path ...

  9. 单源最短路径 Bellman_ford 和 dijkstra

    首先两个算法都是常用于 求单源最短路径 关键部分就在于松弛操作 实际上就是dp的感觉 if (dist[e.to] > dist[v] + e.cost) { dist[e.to] = dist ...

随机推荐

  1. text-decoration:underline与字体重叠

    前几天工作遇到了字体与underline下划线重叠的问题,折腾了半天.今天在张鑫旭的博客上找到了几种解决方法分享一下 1 text-decoration-skip:不推荐使用 17年了这个属性支持率依 ...

  2. java多线程基本概述(一)——线程的基本认知

    1.1.概念: 进程:进程是操作系统结构的基础,是一次程序的执行:是一个程序及其数据再处理器上顺序执行时所发生的活动:是程序再一个数据集合上运行的过程,它是系统进行系统资源分配和调度的最小单元. 线程 ...

  3. Apache日志分割

    1.cronolog安装 采用 cronolog 工具进行 apache 日志分割 http://download.chinaunix.net/download.php?id=3457&Res ...

  4. [转]DevExpress GridControl 关于使用CardView的一点小结

    最近项目里需要显示商品的一系列图片,打算用CardView来显示,由于第一次使用,遇到许多问题,发现网上这方面的资源很少,所以把自己的一点点实际经验小结一下,供自己和大家以后参考. 1.选择CardV ...

  5. iOS开发 socket, 全局socket

    因为项目的要求是全局的socket,  哪里都有可能使用到socket去发消息, 所以我把socket写在了单利里面 项目用的是 pod 'CocoaAsyncSocket'  三方库, 是异步的, ...

  6. Extjs6(一)——用sencha cmd建立一个ExtJs小项目

    本文基于ext-6.0.0 一.用sencha cmd建立一个ExtJs小项目 首先,需要一个命令行工具.进入extjs所在目录. 然后,输入:sencha -sdk [ExtJs6.0文件夹地址] ...

  7. HTML ——Flex弹性布局

    弹性盒布局的使用 1.为父容器添加display:flex或inline-flex属性 (Webkit内核的浏览器,必须加上-webkit前缀.) 容器默认存在两根轴:主轴(main axis)和交叉 ...

  8. JWebFileTrans(JDownload): 一款可以从网络上下载文件的小程序(二)

    一  前言 本文是上一篇博客JWebFileTrans:一款可以从网络上下载文件的小程序(一)的续集.此篇博客主要在上一篇的基础上加入了断点续传的功能,用户在下载中途停止下载后,下次可以读取断点文件, ...

  9. 【lucene系列学习二】Lucene实现高亮显示关键词

    首先,导入下图所示库 然后,import org.apache.lucene.search.highlight.*; 下面,我们新建一个实现高亮显示功能的函数 public static String ...

  10. 蓝桥杯-奇怪的分式-java

    /* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...