SPFA

本文介绍了一种高效的单源最短路径算法——SPFA算法。详细解释了SPFA算法的工作原理,包括如何通过松弛操作更新最短路径长度,如何判断算法是否能够结束,以及如何输出最短路径本身。同时提供了详细的代码示例。
摘要由CSDN通过智能技术生成

最短路径 之 SPFA算法
http://hi.baidu.com/southhill/item/ab26a342590a5aae60d7b967
求最短路径的算法有许多种,除了排序外,恐怕是OI界中解决同一类问题算法最多的了。最熟悉的无疑是Dijkstra,接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径;如果我们想要求出每一对顶点之间的最短路径的话,还可以用Floyd-Warshall。
SPFA是这篇日志要写的一种算法,它的性能非常好,代码实现也并不复杂。特别是当图的规模大,用邻接矩阵存不下的时候,用SPFA则可以很方便地面对临接表。每个人都写过广搜,SPFA的实现和广搜非常相似。
如何求得最短路径的长度值?
首先说明,SPFA是一种单源最短路径算法,所以以下所说的“某点的最短路径长度”,指的是“某点到源点的最短路径长度”。
我们记源点为S,由源点到达点i的“当前最短路径”为D[i],开始时将所有D[i]初始化为无穷大,D[S]则初始化为0。算法所要做的,就是在运行过程中,不断尝试减小D[]数组的元素,最终将其中每一个元素减小到实际的最短路径。
过程中,我们要维护一个队列,开始时将源点置于队首,然后反复进行这样的操作,直到队列为空:
(1)从队首取出一个结点u,扫描所有由u结点可以一步到达的结点,具体的扫描过程,随存储方式的不同而不同;
(2)一旦发现有这样一个结点,记为v,满足D[v] > D[u] + w(u, v),则将D[v]的值减小,减小到和D[u] + w(u, v)相等。其中,w(u, v)为图中的边u-v的长度,由于u-v必相邻,所以这个长度一定已知(不然我们得到的也不叫一个完整的图);这种操作叫做松弛。
引用内容
松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对i,j进行松弛,就是判定是否d[j]>d[i]+w[i,j],如果该式成立则将d[j]减小到d[i]+w[i,j],否则不动。
(3)上一步中,我们认为我们“改进了”结点v的最短路径,结点v的当前路径长度D[v]相比于以前减小了一些,于是,与v相连的一些结点的路径长度可能会相应地减小。注意,是可能,而不是一定。但即使如此,我们仍然要将v加入到队列中等待处理,以保证这些结点的路径值在算法结束时被降至最优。当然,如果连接至v的边较多,算法运行中,结点v的路径长度可能会多次被改进,如果我们因此而将v加入队列多次,后续的工作无疑是冗余的。这样,就需要我们维护一个bool数组Inqueue[],来记录每一个结点是否已经在队列中。我们仅将尚未加入队列的点加入队列。

算法能否结束?
对于不存在负权回路的图来说,上述算法是一定会结束的。因为算法在反复优化各个最短路径长度,总有一个时刻会进入“无法再优化”的局面,此时一旦队列读空,算法就结束了。然而,如果图中存在一条权值为负的回路,就糟糕了,算法会在其上反复运行,通过“绕圈”来无休止地试图减小某些相关点的最短路径值。假如我们不能保证图中没有负权回路,一种“结束条件”是必要的。这种结束条件是什么呢?
思考Bellman-Ford算法,它是如何结束的?显然,最朴素的Bellman-Ford算法不管循环过程中发生了什么,一概要循环|V|-1遍才肯结束。凭直觉我们可以感到,SPFA算法“更聪明一些”,就是说我们可以猜测,假如在SPFA中,一个点进入队列——或者说一个点被处理——超过了|V|次,那么就可以断定图中存在负权回路了。

最短路径本身怎么输出?
在一幅图中,我们仅仅知道结点A到结点E的最短路径长度是73,有时候意义不大。这附图如果是地图的模型的话,在算出最短路径长度后,我们总要说明“怎么走”才算真正解决了问题。如何在计算过程中记录下来最短路径是怎么走的,并在最后将它输出呢?
Path[]数组,Path[i]表示从S到i的最短路径中,结点i之前的结点的编号。注意,是“之前”,不是“之后”。最短路径算法的核心思想成为“松弛”,原理是三角形不等式,方法是上文已经提及的。我们只需要在借助结点u对结点v进行松弛的同时,标记下Path[v] = u,记录的工作就完成了。
输出时可能会遇到一点难处,我们记的是每个点“前面的”点是什么,输出却要从最前面往最后面输,这不好办。其实很好办,见如下递归方法:
程序代码
void PrintPath(int k){
if( Path[k] ) PrintPath(Path[k]);
fout<

include

include

include

include

include

using namespace std;
const long MAXN=10000;
const long lmax=0x7FFFFFFF;
typedef struct
{
long v;
long next;
long cost;
}Edge;

Edge e[MAXN];
long p[MAXN];
long Dis[MAXN];
bool vist[MAXN];
queue q;
long m,n;//点,边
void init()
{
long i;
long eid=0;
memset(vist,0,sizeof(vist));
memset(p,-1,sizeof(p));
fill(Dis,Dis+MAXN,lmax);
while (!q.empty())
{
q.pop();
}
for (i=0;i

include

include

using namespace std;
const long MAXN=10000;
const long lmax=0x7FFFFFFF;
typedef struct
{
long v;
long next;
long cost;
}Edge;

Edge e[MAXN];
long p[MAXN];
long Dis[MAXN];
bool vist[MAXN];
queue q;
long m,n;//点,边
void init()
{
long i;
long eid=0;
memset(vist,0,sizeof(vist));
memset(p,-1,sizeof(p));
fill(Dis,Dis+MAXN,lmax);
while (!q.empty())
{
q.pop();
}
for (i=0;i

include

include

include

using namespace std;

define MAX_NUM 1000000001

define MAX_DOTNUM 1000001

int n,m;
queuemyqueue;
bool mark[MAX_DOTNUM];
__int64 dis[MAX_DOTNUM];

struct node
{
int v;
int w;
node *next;
}edge[MAX_DOTNUM];//此邻接表用于存储正向图
node reversed_edge[MAX_DOTNUM];//此逆邻接表用于存储逆向图
void initial(node edge[])//邻接表的初始化,里面封装了回收上一次操作所分配之内存的操作
{
int i;
node *p;
node *q;
for(i=1;i<=n;i++)
{
p=&edge[i];
q=p->next;
while(q!=NULL)
{
p->next=q->next;
delete q;
q=p->next;
}
}
}

void input_case()//每一个case的输入函数
{
int i;
for(i=1;i<=m;i++)
{
node *p;
node *q;
int a,b,c;
scanf(“%d%d%d”,&a,&b,&c);
/**/
p=&edge[a];
q=new node;
q->v=b;
q->w=c;
q->next=p->next;
p->next=q;
/**/
p=&reversed_edge[b];
q=new node;
q->v=a;
q->w=c;
q->next=p->next;
p->next=q;
}
}

void spfa(node edge[])//SPFA部分
{
int i;
/**
memset(mark,false,sizeof(mark));
for(i=1;i<=n;i++)
dis[i]=MAX_NUM;
while(myqueue.size()!=0)
myqueue.pop();
/**
dis[1]=0;
mark[1]=true;
myqueue.push(1);
while(myqueue.size()!=0)//如果队列不空,则进行松弛操作,直到队列空为止
{
int temp=myqueue.front();
myqueue.pop();
mark[temp]=false;
node *p;
for(p=edge[temp].next;p!=NULL;p=p->next)
{
if(dis[p->v]>dis[temp]+p->w)
{
dis[p->v]=dis[temp]+p->w;
if(mark[p->v]!=true)
{
myqueue.push(p->v);
mark[p->v]=true;
}
}
}
}
}

int main()
{
int testcase;
int i,j;
__int64 sum;
scanf(“%d”,&testcase);
for(i=1;i<=MAX_DOTNUM-1;i++)
{
edge[i].v=i;
edge[i].w=0;
edge[i].next=NULL;
}
for(i=1;i<=MAX_DOTNUM-1;i++)
{
reversed_edge[i].v=i;
reversed_edge[i].w=0;
reversed_edge[i].next=NULL;
}
for(i=1;i<=testcase;i++)
{
sum=0;
scanf(“%d%d”,&n,&m);
initial(edge);
initial(reversed_edge);
input_case();
spfa(edge);
for(j=1;j<=n;j++)
sum+=dis[j];
spfa(reversed_edge);
for(j=1;j<=n;j++)
sum+=dis[j];
printf(“%I64d\n”,sum);
}
system(“pause”);
return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值