最短路SPFA
2014-02-18 20:22
281 查看
SPFA算法:求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm,是Bellman-Ford算法0(VE)的一个优化,期望的时间复杂度O(2E),E为图的边数,所以SPFA用在稀疏图上的效果会更加明显。SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。
很多时候,给定的图存在负权边,这时类似Dijkstra算法(0(V^2),在稠密图上有优势)便没有了用武之地,而bellman_ford的复杂度又过高,SPFA算法便派上用场了。
算法核心思想:理解这个算法,最好先看看Bellman-Ford,因为他是对Bellman-Ford的一个优化,SPFA算法采用了一个队列,优化算法,用数组dict[]记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
需要注意的是:仅当图不存在负权回路时,SPFA能正常工作。如果图存在负权回路,由于负权回路上的顶点无法收敛,总有顶点在入队和出队往返,队列无法为空,这种情况下SPFA无法正常结束。
1 记录每个结点进队次数,超过|V|次表示有负权
2 记录这个结点在路径中处于的位置,ord[i],每次更新的时候ord[i]=ord[x]+1,若超过|V|则表示有负圈....
下面举一个实例来说明SFFA算法是怎样进行的:
设有一个有向图G={V,E},其中,V = {V0,V1,V2,V3,V4},E
={<V0,V1>,<V0,V4>,<V1,V2>,<V1,V4>,<V2,V3>,<V3,V4>,<V4,V2>} = {2,10,3,7,4,5,6},见下图:
算法执行时各步的Queue队的值和D数组的值由下表所示:
算法执行到第五步后,队Queue空,算法结束。源点V0到V1的最短路径为2,到V2的最短路径为5,到V3的最短路径为9,到V4的最短路径为9,结果显然是正确的。
SPFA算法模板1:(邻接矩阵)
const int nMax = 1000; // 顶点的数量上限。
const int eMax = 2000; // 边的数量上限。
const int inf = 0xffffff;
int n, edge[nMax][nMax], dict[nMax];
int queue[eMax]; // 创建新队列,也可以用STL中的#include<queue>,但效率较低。
bool vis[nMax];
void init_data(s){
for(i = 1; i <= n; i ++) // 初始化dict[]。
dict[i] = inf;
dict[s] = 0; // 起点s。
}
void spfa(int s){ // 初始结点s,即为起点,若目标结点t,则输出dict[t]。
init_data(s);
int head = 0, tail = 1;
// int path[Max]; // 可增加一个path[]数组来记录最后s到t的路径。
queue[0] = x;
dict[s] = 0;
while(tail > head){
int u = queue[head];
vis[u] = true;
for(i = 1; i <= n; i ++){
if(dict[i] > dict[u] + edge[u][i]){
dict[i] = dict[u] + edge[u][i];
// path[i] = u;
if(!vis[i]){ // 对以前没有访问过的顶点,加入队列中。
vis[i] = true;
queue[tail] = i;
tail ++;
}
}
}
vis[u] = false; // 记得把出队列的顶点的vis[]置为false。
head ++;
}
}
SPFA算法模板2:(邻接表)
#include<iostream>
using namespace std;
#define MAXE 10000
#define MAXV 1000
int inf = 1000000000;
struct edge_t
{
int s, t;
int w;
int next;
};
edge_t arrEdge[MAXE]; //arrEdge[1...szEdge]保存szEdge条边.
int szEdge;
int arrHead[MAXV];
int nv;
int arrPre[MAXV];
int arrDis[MAXV];
int Que[MAXE*10]; //队列
char inQue[MAXV]; //点v是否在队列中
void SPFA(int s)
{
int i, ie, v, head, tail;
edge_t e;
for(i = 1; i <= nv; i++)
arrDis[i] = inf, arrPre[i] = i;
arrDis[s] = 0;
arrPre[s] = -1;
//初始化队列
memset(Que, 0, sizeof(Que));
memset(inQue, 0, sizeof(inQue));
head = 0, tail = 1;
Que[head] = s;
inQue[s] = 1;
while(head < tail)
{
v = Que[head++];
inQue[v] = 0;
for(ie = arrHead[v]; ie!=-1; ie = arrEdge[ie].next)
{
e = arrEdge[ie];
if(arrDis[e.t] > arrDis[v] + e.w)
{
arrDis[e.t] = arrDis[v] + e.w, arrPre[e.t] = v;
if(!inQue[e.t])
{
inQue[e.t] = 1;
Que[tail++] = e.t;
}
}
}
}
return ;
}
int main()
{
int ne, i, s, t, w, p;
memset(arrHead, -1, sizeof(arrHead));
memset(arrEdge, 0, sizeof(arrEdge));
szEdge = 0;
scanf("%d %d", &nv, &ne);
for(i = 1; i <= ne; i++)
{
scanf("%d %d %d", &s, &t, &w);
szEdge++;
arrEdge[szEdge].s = s;
arrEdge[szEdge].t = t;
arrEdge[szEdge].w = w;
arrEdge[szEdge].next = arrHead[s];
arrHead[s] = szEdge;
szEdge++;
arrEdge[szEdge].s = t;
arrEdge[szEdge].t = s;
arrEdge[szEdge].w = w;
arrEdge[szEdge].next = arrHead[t];
arrHead[t] = szEdge;
}
SPFA(1);
for(i = 1; i <= nv; i++)
{
printf("Distance from 1 to %d is %d.\nPath: %d", i, arrDis[i], i);
p = arrPre[i];
while(p != -1)
{
printf(" <-- %d", p);
p = arrPre[p];
}
printf("\n\n");
}
return 0;
}
很多时候,给定的图存在负权边,这时类似Dijkstra算法(0(V^2),在稠密图上有优势)便没有了用武之地,而bellman_ford的复杂度又过高,SPFA算法便派上用场了。
算法核心思想:理解这个算法,最好先看看Bellman-Ford,因为他是对Bellman-Ford的一个优化,SPFA算法采用了一个队列,优化算法,用数组dict[]记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
需要注意的是:仅当图不存在负权回路时,SPFA能正常工作。如果图存在负权回路,由于负权回路上的顶点无法收敛,总有顶点在入队和出队往返,队列无法为空,这种情况下SPFA无法正常结束。
1 记录每个结点进队次数,超过|V|次表示有负权
2 记录这个结点在路径中处于的位置,ord[i],每次更新的时候ord[i]=ord[x]+1,若超过|V|则表示有负圈....
下面举一个实例来说明SFFA算法是怎样进行的:
设有一个有向图G={V,E},其中,V = {V0,V1,V2,V3,V4},E
={<V0,V1>,<V0,V4>,<V1,V2>,<V1,V4>,<V2,V3>,<V3,V4>,<V4,V2>} = {2,10,3,7,4,5,6},见下图:
算法执行时各步的Queue队的值和D数组的值由下表所示:
算法执行到第五步后,队Queue空,算法结束。源点V0到V1的最短路径为2,到V2的最短路径为5,到V3的最短路径为9,到V4的最短路径为9,结果显然是正确的。
SPFA算法模板1:(邻接矩阵)
const int nMax = 1000; // 顶点的数量上限。
const int eMax = 2000; // 边的数量上限。
const int inf = 0xffffff;
int n, edge[nMax][nMax], dict[nMax];
int queue[eMax]; // 创建新队列,也可以用STL中的#include<queue>,但效率较低。
bool vis[nMax];
void init_data(s){
for(i = 1; i <= n; i ++) // 初始化dict[]。
dict[i] = inf;
dict[s] = 0; // 起点s。
}
void spfa(int s){ // 初始结点s,即为起点,若目标结点t,则输出dict[t]。
init_data(s);
int head = 0, tail = 1;
// int path[Max]; // 可增加一个path[]数组来记录最后s到t的路径。
queue[0] = x;
dict[s] = 0;
while(tail > head){
int u = queue[head];
vis[u] = true;
for(i = 1; i <= n; i ++){
if(dict[i] > dict[u] + edge[u][i]){
dict[i] = dict[u] + edge[u][i];
// path[i] = u;
if(!vis[i]){ // 对以前没有访问过的顶点,加入队列中。
vis[i] = true;
queue[tail] = i;
tail ++;
}
}
}
vis[u] = false; // 记得把出队列的顶点的vis[]置为false。
head ++;
}
}
SPFA算法模板2:(邻接表)
#include<iostream>
using namespace std;
#define MAXE 10000
#define MAXV 1000
int inf = 1000000000;
struct edge_t
{
int s, t;
int w;
int next;
};
edge_t arrEdge[MAXE]; //arrEdge[1...szEdge]保存szEdge条边.
int szEdge;
int arrHead[MAXV];
int nv;
int arrPre[MAXV];
int arrDis[MAXV];
int Que[MAXE*10]; //队列
char inQue[MAXV]; //点v是否在队列中
void SPFA(int s)
{
int i, ie, v, head, tail;
edge_t e;
for(i = 1; i <= nv; i++)
arrDis[i] = inf, arrPre[i] = i;
arrDis[s] = 0;
arrPre[s] = -1;
//初始化队列
memset(Que, 0, sizeof(Que));
memset(inQue, 0, sizeof(inQue));
head = 0, tail = 1;
Que[head] = s;
inQue[s] = 1;
while(head < tail)
{
v = Que[head++];
inQue[v] = 0;
for(ie = arrHead[v]; ie!=-1; ie = arrEdge[ie].next)
{
e = arrEdge[ie];
if(arrDis[e.t] > arrDis[v] + e.w)
{
arrDis[e.t] = arrDis[v] + e.w, arrPre[e.t] = v;
if(!inQue[e.t])
{
inQue[e.t] = 1;
Que[tail++] = e.t;
}
}
}
}
return ;
}
int main()
{
int ne, i, s, t, w, p;
memset(arrHead, -1, sizeof(arrHead));
memset(arrEdge, 0, sizeof(arrEdge));
szEdge = 0;
scanf("%d %d", &nv, &ne);
for(i = 1; i <= ne; i++)
{
scanf("%d %d %d", &s, &t, &w);
szEdge++;
arrEdge[szEdge].s = s;
arrEdge[szEdge].t = t;
arrEdge[szEdge].w = w;
arrEdge[szEdge].next = arrHead[s];
arrHead[s] = szEdge;
szEdge++;
arrEdge[szEdge].s = t;
arrEdge[szEdge].t = s;
arrEdge[szEdge].w = w;
arrEdge[szEdge].next = arrHead[t];
arrHead[t] = szEdge;
}
SPFA(1);
for(i = 1; i <= nv; i++)
{
printf("Distance from 1 to %d is %d.\nPath: %d", i, arrDis[i], i);
p = arrPre[i];
while(p != -1)
{
printf(" <-- %d", p);
p = arrPre[p];
}
printf("\n\n");
}
return 0;
}
相关文章推荐
- Estrella's Travel (最短路spfa)
- hdu4725The Shortest Path in Nya Graph(拆点 + 最短路dijkstra | SPFA)
- HDU5137 How Many Maos Does the Guanxi Worth(最短路,spfa)
- spfa 快速最短路-队列优化
- 洛谷P1144 最短路计数(SPFA)
- poj 2449 求第k最短路 A* + SPFA
- 【最短路】【spfa】CDOJ1647 酌贪泉而觉爽, 处涸辙以犹欢。
- 最短路 SPFA
- codevs 1835 魔法猪学院 A*寻k短路做了一个月卡死在spfa那了/(ㄒoㄒ)/~~
- ACMclub - 1124 成语接龙 (最短路,SPFA)
- 蓝桥杯 最短路(spfa(优先队列+迪杰斯特拉))
- 最短路之--spfa
- poj 1511 Invitation Cards(spfa最短路)
- POJ3268 Silver Cow Party spfa求解 最短路
- hdu 1839 Delay Constrained Maximum Capacity Path 二分下限+最短路spfa
- 【最短路】【spfa】hdu6071 Lazy Running
- 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
- 例题11-11 赛车比赛 单源最短路 spfa
- Invitation Cards POJ - 1511 最短路spfa
- hdu 6181 Two Paths(最短路,spfa)