最短路径 之 SPFA算法

搜索图论 - spfa 算法 spfa 算法 阅读详情

最短路径 之 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<<k<<' ';
}


SPFA的代码怎么写?

我写了邻接表和邻接矩阵两种,两者想像起来是那么的不同,算法的思路上实在区别不大,只是用不同方式诠释“扫描”的过程而已。只给出SPFA的单个函数,我不觉得很容易看懂,但是我仍然把两个程序的SPFA函数放在下面。在日志的结尾处,有一个完整版文件下载。贴程序,首先是邻接表的:

程序代码
void SPFA(){
    for(int i=1; i<=gv; i++)
        Dist[i] = 100000;
    Dist[S] = 0;
    int closed = 0, open = 1;
    queue[1] = S;
    Inqueue[S] = true;
    do{
        closed++;
        node *tmp = connect[queue[closed]];
        Inqueue[queue[closed]] = false;
        while(tmp != NULL){
            if( Dist[tmp->key] > Dist[queue[closed]] + tmp->w ){
                Dist[tmp->key] = Dist[queue[closed]] + tmp->w;
                Path[tmp->key] = queue[closed];
                if( !Inqueue[tmp->key] ){
                    Inqueue[tmp->key] = true;
                    open++;
                    queue[open] = tmp->key;
                }
            }
            tmp = tmp->next;
        }
    }while(closed < open);
}

然后是邻接矩阵的:

程序代码
void SPFA(){
    for( int i=1; i<=gv; i++){
        Dist[i] = 100000;
        for( int j=1; j<=gv; j++)
            if( !Graph[i][j] && i!=j) Graph[i][j] = 100000;
    }
    int closed = 0, open = 1;
    queue[1] = S;
    Dist[S] = 0;
    do{
        closed++;
        int u = queue[closed];
        Inqueue[u] = false;
        for(int i=1; i<=gv; i++)
            if ( Dist[i] > Dist[u] + Graph[u][i] ){
                Dist[i] = Dist[u] + Graph[u][i];
                Path[i] = u;
                if( !Inqueue[i] ){
                    Inqueue[i] = true;
                    open++;
                    queue[open] = i;
                }
            }
    }while(closed < open);
}


spfa算法 Easy sssp 收藏 
输入数据给出一个有N(2 <= N <= 1,000)个节点,M(M <= 100,000)条边的带权有向图. 
要求你写一个程序, 判断这个有向图中是否存在负权回路. 如果从一个点沿着某条路径出发, 又回到了自己, 而且所经过的边上的权和小于0, 就说这条路是一个负权回路.
如果存在负权回路, 只输出一行-1;
如果不存在负权回路, 再求出一个点S(1 <= S <= N)到每个点的最短路的长度. 约定: S到S的距离为0, 如果S与这个点不连通, 则输出NoPath.

INPUT:
第一行: 点数N(2 <= N <= 1,000), 边数M(M <= 100,000), 源点S(1 <= S <= N);
以下M行, 每行三个整数a, b, c表示点a, b(1 <= a, b <= N)之间连有一条边, 权值为c(-1,000,000 <= c <= 1,000,000)

OUTPUT:
如果存在负权环, 只输出一行-1, 否则按以下格式输出
共N行, 第i行描述S点到点i的最短路: 
如果S与i不连通, 输出NoPath;
如果i = S, 输出0;
其他情况输出S到i的最短路的长度

INPUT:
6 8 1
1 3 4
1 2 6
3 4 -7
6 4 2
2 4 5
3 6 3
4 5 1
3 5 4

OUTPUT:
0
6
4
-3
-2
7

注意:
题目说的不是很清楚,给出的图不一定是完全联通图,有些是断开的几个图,所以在判断的源点是否有环以外还要分别对不同的点进行spfa呀。再进行分别的判断和输出。

有几个优化:
1.可以先判断是否有负权自环,有则直接输出-1
2.在枚举的过程中,当这个顶点的最短路(d[i])<0时,有负权回路,输出-1.

【参考程序】:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
long queue[1001],a[1001],psum[1001],dis[1001],l[1001][1001],cost[1001][1001];
long n,m,s,i,j;
bool hash[1001],bk;
void spfa(int s)
{
    int head,tail,start,now,i;
    for (i=1;i<=n;i++)
    {
        dis[i]=0xfffffff;
        psum[i]=0;
        hash[i]=false;
    }
    head=tail=1;hash[s]=true;
    psum[s]=1;dis[s]=0;queue[1]=s;
    while (head<=tail)
    {
        start=queue[(head-1)%n+1];
        hash[start]=true;
        for (i=1;i<=l[start][0];i++)
        {
            now=l[start][i];
            if (dis[now]>dis[start]+cost[start][now])
            {
                dis[now]=dis[start]+cost[start][now];
                if (!hash[now])
                {
                    hash[now]=true;
                    tail++;
                    queue[(tail-1)%n+1]=now;
                    psum[now]++;
                    if (psum[now]>n)
                    {//记录每个点进队的次数(判断环的关键}
                        bk=false;
                        return;
                    }
                }
            }
        }
        head++;
        hash[start]=false;
        if (dis[s]<0)
        {//判断环的一个优化
            bk=false;
            return;
        }
    }
}
void output()
{
    bk=true;
    spfa(s);
    if (!bk)
    {
        printf("-1\n");
        return;
    }
    memcpy(a,dis,sizeof(long)*(n+1));
    for (i=1;i<=n;i++)
      if (a[i]==0xfffffff)
      {
            bk=true;
            spfa(i);
            if (!bk)
            {
                printf("-1\n");
                return;
            }
      }
    for (i=1;i<=n;i++)
      if (a[i]==0xfffffff) printf("NoPath\n");
      else printf("%d\n",a[i]);
}
void input()
{
    scanf("%d%d%d",&n,&m,&s);
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        if (i==j) cost[i][j]=0;
        else cost[i][j]=0xfffffff;
    memset(l,0,sizeof(l));
    int x,y,c;
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&c);
        if (c<cost[x][y])
        {
            cost[x][y]=c;
            l[x][0]++;
            l[x][l[x][0]]=y;
        }
    }
}
int main()
{
    input();
    output();
    system("pause");
    return 0;
}

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bobcowwocb/archive/2009/09/14/4550188.aspx

2009年07月24日 星期五 15:10
SPFA算法模版+邻接表实现 
SPFA即shotest path faster algorithm,由意思就可以看出该算法效率比较高。

其实SPFA就是bellman-ford算法的一个优化。

具体做法是用一个队列保存待松弛的点,然后对于每个出队的点依次遍历每个与他有边相邻的点(用邻接表效率较高),如果该点可以松弛并且队列中没有该点则将它加入队列中,如此迭代直到队列为空。

据说平均效率是O(E),可见对边稀疏的图用此算法效果是相当可观的。

若要判负环路,则记录一个点的入队次数,若超过边数,则有负权环。

#include <iostream>
#include <queue>
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<long> 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<n;++i)
    {
        long from,to,cost;
        scanf("%ld %ld %ld",&from,&to,&cost);

        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

        //以下适用于无向图
        swap(from,to);
        
        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

    }
}

void print(long End)
{
    //若为lmax 则不可达
    printf("%ld\n",Dis[End]);    
}

void SPF()
{

    init();

    long Start,End;
    scanf("%ld %ld",&Start,&End);
    Dis[Start]=0;
    vist[Start]=true;
    q.push(Start);

    while (!q.empty())
    {
        long t=q.front();
        q.pop();
        vist[t]=false;
        long j;
        for (j=p[t];j!=-1;j=e[j].next)
        {
            long w=e[j].cost;
            if (w+Dis[t]<Dis[e[j].v])
            {
                Dis[e[j].v]=w+Dis[t];
                if (!vist[e[j].v])
                {
                    vist[e[j].v]=true;
                    q.push(e[j].v);
                }
            }
        }
    }

    print(End);

}

int main()
{
    while (scanf("%ld %ld",&m,&n)!=EOF)
    {
        SPF();
    }
    return 0;
}



(请您对文章做出评价)

一、Bellman-Ford算法

最优性原理


它是最优性原理的直接应用,算法基于以下事实:

l          如果最短路存在,则每个顶点最多经过一次,因此不超过n-1条边;

l          长度为k的路由长度为k-1的路加一条边得到;

l          由最优性原理,只需依次考虑长度为1,2,…,k-1的最短路。

适用条件&范围

l          单源最短路径(从源点s到其它所有顶点v);

l          有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);

l          边权可正可负(如有负权回路输出错误提示);

l          差分约束系统(需要首先构造约束图,构造不等式时>=表示求最小值, 作为最长路,<=表示求最大值, 作为最短路。<=构图时, 有负环说明无解;求不出最短路(为Inf)为任意解。>=构图时类似)。

算法描述

l          对每条边进行|V|-1次Relax操作;

l          如果存在(u,v)∈E使得dis[u]+w<dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。

时空复杂度                                                                                           

for i:=1 to |V|-1 do

    for 每条边(u,v)∈E do   Relax(u,v,w);

for每条边(u,v)∈E do

if dis[u]+w<dis[v] Then Exit(False)

算法时间复杂度O(VE)。因为算法简单,适用范围又广,虽然复杂度稍高,仍不失为一个很实用的算法。

改进和优化   如果循环n-1次以前已经发现不存在紧边则可以立即终止; Yen氏改进(不降低渐进复杂度);SPFA算法

二、             SPFA算法

算法简介 
SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。 它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。

算法流程 
SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的 改变。因此,算法大致流程是用一个队列来进行维护,即用一个先进先出的队列来存放被成功松弛的顶点。初始时,源点s入队。当队列不为空时,取出队首顶点, 对它的邻接点进行松弛。如果某个邻接点松弛成功,且该邻接点不在队列中,则将其入队。经过有限次的松弛操作后,队列将为空,算法结束。SPFA算法的实 现,需要用到一个先进先出的队列 queue 和一个指示顶点是否在队列中的标记数组mark。为了方便查找某个顶点的邻接点,图采用临界表存储。

算法代码 
Procedure SPFA;Begin             initialize-single-source(G,s);             initialize-queue(Q);             enqueue(Q,s);             while not empty(Q) do begin                u:=dequeue(Q);                for each v∈adj[u] do begin                   tmp:=d[v]; relax(u,v);                   if (tmp<>d[v]) and (not v in Q) then enqueue(v);                   end;                end;End;负环处理
   需要特别注意的是:仅当图不存在负权回路时,SPFA能正常工作。如果图存在负权回路,由于负权回路上的顶点无法收敛,总有顶点在入队和出队往返,队列无法为空,这种情况下SPFA无法正常结束。

判断负权回路的方案很多,世间流传最广、比较容易实现并且高效的方法的是记录每个结点进队次数,超过|V|次表示有负权。

三、             学以致用

POJ 1201 Intervals 差分约束系统

设S(i)为 0..i-1 中在最终序列中的的整数个数。则约束条件如下:

S(b)-S(a) >= c

0 <= S(i+1) - S(i) <= 1 <==> S(i+1)-S(i) >= 0;

                             S(i)-S(i+1) >= -1

注意本题要求的是最小值, 而按照>=号建图后发现图中有负环, 怎么办呢?

其实很简单, 本题求的不是最短路, 而是最长路! Bellman_ford即可!

POJ 1275 Cashier Employment 出纳员的雇佣

黑书上有详细讲解

POJ 1364 King 差分约束系统

这个题目构图之后, 只需要用bellman_ford判断是否有负圈.

构图方法:

首先进行转换:a[j]+...+a[j+m] = a[1]+...a[j+m] - (a[1]+...+a[j-1]) = sum[j+m] -


sum[j-1] >(<) ki. 差分约束只能全部是<=或者(>=).

第二步转换: sum[j+m]-sum[j-1] <= ki-1 或者 sum[j-1]-sum[j+m] <= -ki-1.

约束图构造好后就是简单的Bellman-Ford了!

POJ 1716 Integer Intervals 是1201的简单版本, 贪心算法能够得到更好的效果.

POJ 2983 Is the Information Reliable?

差分约束题, 处理一下等号的情况, 然后普通的Bellman_ford

POJ 3159 Candies 最短路径

Bellman-Ford超时, Dijkstra算法可以高效解决, SPFA(队列)居然超时...SPFA修改为堆栈实现就过了.

POJ 3169 Layout 差分约束

Bellman-Ford 和 SPFA 实现均可

POJ 3259 Wormholes 判断负权回路

TOJ 2976 Path 单纯的最短路径 可练习SPFA

ZOJ 3033 Board Games 我做的第一道Bellman-Ford题目

首先,DFS判断可达性,不可达直接输出infinity结束,可达,bellman-ford判断是否存在负环,存在输出infinity,否则,输出最短距离。

SPFA算法模版+邻接表实现 
SPFA即shotest path faster algorithm,由意思就可以看出该算法效率比较高。

其实SPFA就是bellman-ford算法的一个优化。

具体做法是用一个队列保存待松弛的点,然后对于每个出队的点依次遍历每个与他有边相邻的点(用邻接表效率较高),如果该点可以松弛并且队列中没有该点则将它加入队列中,如此迭代直到队列为空。

据说平均效率是O(E),可见对边稀疏的图用此算法效果是相当可观的。

若要判负环路,则记录一个点的入队次数,若超过边数,则有负权环。

#include <iostream>
#include <queue>
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<long> 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<n;++i)
    {
        long from,to,cost;
        scanf("%ld %ld %ld",&from,&to,&cost);

        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

        //以下适用于无向图
        swap(from,to);
        
        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

    }
}

void print(long End)
{
    //若为lmax 则不可达
    printf("%ld\n",Dis[End]);    
}

void SPF()
{

    init();

    long Start,End;
    scanf("%ld %ld",&Start,&End);
    Dis[Start]=0;
    vist[Start]=true;
    q.push(Start);

    while (!q.empty())
    {
        long t=q.front();
        q.pop();
        vist[t]=false;
        long j;
        for (j=p[t];j!=-1;j=e[j].next)
        {
            long w=e[j].cost;
            if (w+Dis[t]<Dis[e[j].v])
            {
                Dis[e[j].v]=w+Dis[t];
                if (!vist[e[j].v])
                {
                    vist[e[j].v]=true;
                    q.push(e[j].v);
                }
            }
        }
    }

    print(End);

}

int main()
{
    while (scanf("%ld %ld",&m,&n)!=EOF)
    {
        SPF();
    }
    return 0;
}
POJ 1511-Invitation Cards(SPFA算法) 
今天终于用SPFA写出了第一个程序,感觉收获很大,从Dij到Floyed再到Bellmen,以及今天的SPFA,每一种算法背后都蕴藏着许多值得思考的地方。正因为研究了它们,才使得我的能力不断地获得了提高。
之前以为SPFA做为最短路问题最快的算法,想必代码定不好写,不过今天研究过才知道,SPFA的代码量远远不及Dij,这着实令人惊叹,原来最好的算法SPFA是如此的好写,呵呵 我想此算法在很大程度上可以完全代替之前的算法,以后再碰到最短路问题时,SPFA一定能成为首要的选择!
PS:由于是用邻接表来存储的,所以每次操作前要收回以前分配的内存,我尝试了收回和不收回两种方法,发现其实差别不大,如果纯粹是比赛的话,可能不收回反而会更好些(避免超时)。当然如果在实际应用中,应该切记内存的分配,否则软件可能会发生异常。


//Coded by abilitytao 
//Time:2009-04-10 22:49:58
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;
#define MAX_NUM 1000000001
#define MAX_DOTNUM 1000001

int n,m;
queue<int>myqueue;
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;

}

最短路径问题---SPFA算法详解 前言 博客编写人:Willam 博客编写时间:2017/3/12 博主邮箱:2930526477@qq.com(有志同道合之人,可以加qq交流交流编程心得) 1、最短路径问题介绍 问题解释: 从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径 解决问题的算法: 迪杰斯特拉算法(Dijkstra算法) 弗洛伊德算法(Floyd算法SPFA... 阅读详情

相关推荐

SPFA算法详解:高效的单源最短路径算法

介绍spfa算法求解最短路和判断负环的思路

Lostgreen的博客 1996

最短路径——SPFA

<br /><br />Posted on 2009-10-30 14:00 小强摩羯座 阅读(350) 论(0) 编辑 收藏 所属分类: 算法编程 <br />最短路径SPFA算法 (转载)(2009-05-06 20:41:51)<br />标签:spfa 杂谈   <br />求最短路径算法有许多种,除了排序外,恐怕是OI界中解决同一类问题算法最多的了。最熟悉的无疑是Dijkstra,接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径;如果我们想要求出每一对顶点

hqd_acm的专栏 3176

最短路径Bellman-Ford算法SPFA算法详解

最短路径Bellman-Ford算法SPFA算法详解

m0_72674633的博客 1674

最短路和差分约束(三种算法实现)( Til the Cows Come Home )

题目训练链接(密码hpuacm): https://vjudge.net/contest/246705 我会分别用 迪杰斯特拉  优先队列和链式前向星优化过的迪杰斯特拉  SPFA算法 三种方法讲一下例题。 此外上述三种算法是求单源最短路问题, 这里还会介绍一下多源最短路的算法 floyd算法。多源最短路可以求出任意两点间的最短距离。 在存图方式中会用到邻接矩阵 链式前向星等存图方式,不知道...

成龙大侠的博客 2600

SPFA算法-单源最短路径算法

1.介绍:   SPFA算法单源最短路径算法,一种高效的最短路径算法! 2.思路   (1)初始化     1>源点路径为0 :d[s]=0 ,其中s为源点     2>初始化d[N]为无穷大,即d[i]表示,源点s到i为无穷大INF     3>p[N]初始化为源点s或-1,表示没有前驱   (2)队列+松弛     1>读取队头顶点u,并...

asdfghjkl978564的博客 341

SPFA 算法实现原理及其应用

SPFA算法,全称为Shortest Path Faster Algorithm,是求解单源最短路径问题的一种常用算法,它可以处理有向图或者无向图,边权可以是正数、负数,但是不能有负环。

MissingPastDay的博客 3234

最短路径算法SPFA算法

关于求最短路径:        求最短路径算法有许多种,除了排序外,恐怕是OI界中解决同一类问题算法最多的了。最熟悉的无疑是Dijkstra(不能求又负权边的图),接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径;如果我们想要求出每一对顶点之间的最短路径的话,还可以用Floyd-Warshall。 关于松弛:       ...

qq_42848319的博客 2574

最短路径算法SPFA算法

关于求最短路径之一的SPAF算法的总结。 简单的介绍了松弛,求最短路径算法。 重点说明了SPAF算法的过程。 里面有强大的图解,有详尽的说明,加之对此算法的理解。

徵羽調_xiao賤的专栏 5863

最短路径问题——SPFA算法

SPFA算法是Bellman-Ford的队列优化版本,用于求解单源最短路径问题。其通过动态松弛操作,仅处理距离更新的节点,显著提升了效率。该算法在稀疏图和含负权边的场景中表现优异,平均时间复杂度接近O(kE),且具备负环检测能力。然而,其最坏时间复杂度仍为O(VE),对数据敏感且效率不稳定。相比Dijkstra算法SPFA能处理负权边但缺乏理论保证;相比原Bellman-Ford算法,它通过队列优化减少了冗余计算。实际应用中,需根据图结构特点(是否有负权边、稀疏程度等)在算法间权衡选择,SPFA特别适合含

2303_79102751的博客 1164

SPFA算法——最短路径

  粗略讲讲SPFA算法的原理,SPFA算法是1994年西安交通大学段凡丁提出 是一种求单源最短路的算法 算法中需要用到的主要变量 int n;  //表示n个点,从1到n标号 int s,t;  //s为源点,t为终点 int d[N];  //d[i]表示源点s到点i的最短路 int p[N];  //记录路径(或者说记录前驱) queue &lt;int&gt; q;  //...

weixin_34277853的博客 912

SPFA算法单源最短路径

SPFA算法单源最短路径 算法简介:SPFA算法是应用于有向图上的一种算法,常用于求出从源点s到其它所有点的最短路径。相对于Dijkstra的O(n^2)的复杂度,SPFA的复杂度仅仅边有关,为O(KE),其中E为该图的边数,且可以证明K < 2,故对于稀疏图来说,SPFA相较于Dijkstra要快上不少。算法思想:SPFA算法是基于广搜的思想实现它O(KE)的复杂度的。这里我用vector存放

lhz_cnyali的博客 7万+

最短路径算法——SPFA算法

在家的这几天效率还是蛮低的,总是有其他的事情耽搁,然后最近就在做搜索题,然后做了两个搜索关于最短路径的,点数少的时候之前学的佛洛依德还能派上点用处,可以点数过1000之后就容易超时了,然后看他们的题解,都用到了一个SPFA算法 SPFA算法是用来求某个点到别的点的距离的最短/最长路径的 算法的整体思路就是使用队列不断地更新点的最短路径知道无法更新为止(感觉和BFS差不多,感觉区别就是进队的数据还是可以继续进队的) 不解释了,直接上代码吧(感觉解释不太明白hhh) a[i][j]:邻接矩阵,记录邻接点 w[i

qq_51769081的博客 826

SPFA算法最短路径算法

SPFA-图论-最小路径

m0_64045085的博客 2万+

SPFA算法详解

前置知识:Bellman-Ford算法 前排提示:SPFA算法非常容易被卡出翔。所以如果不是图中有负权边,尽量使用Dijkstra!(Dijkstra算法不能能处理负权边,但SPFA能) 前排提示*2:一定要先学Bellman-Ford! 0.引子 在Bellman-Ford算法中,每条边都要松弛\(n-1\)轮,还不一定能松弛成功,这实在是太浪费了。能不能想办法改进呢? 非常幸运,SPFA算法...

qq_14886903的博客 6267

SPFA详解

引子定义SPFA是Shortest Path Faster Algorithm,是Bellman-Ford算法的改进版。和其他最短路算法一样,都是以松弛操作的三角形不等式为基础操作的。优点SPFA算法用途广,适应负权,还能判断正环和负环……在差分约束建模中也有重大用处……SPFA是个好东西SPFA实现spfa有两种实现方式,一种是栈实现,一种是队列实现。 在有负环的情况下,栈比队列更快,但是如果

蒟蒻柴犬首相的博客 4454

搜索图论 --- 最短路 - SPFA 算法、Floyd 算法

第一步,只要队列不空,队列里面还有节点变小的话,先从队列里面取出队头,然后把队头删掉,第二步遍历t的所有出边,如果t变小的话,所有以t为起点的边的终点都有可能变小,更新t的所有出边,例如t→b,距离是w,第三步,如果更新成功的话,把待更新的点b加入队列,加入之前需要判断一下,如果队列之前已经有b的话,b就不用再重复加入了。基本思路就是更新过谁,再拿谁来更新别人,一个点如果没有被更新过的话,那么它再去更新别人一定是没有效果的,只有我变小了,我后面的人才会变小。...

weixin_60569662的博客 2207

最短路径算法的编程实现 C语言

1.掌握最短路径算法的基本原理及编程实现;operating system version:Win11 CPU instruction set: x64 Integrated Development Environment:Viusal Studio 20221)建立一张图,选择一种存储结构(邻接矩阵或邻接表)初始化该图; 2)用Dijkstra算法实现点之间的最短路径。1) 实现图的两种表示方法; 2) 实现Dijkstra算法;1. 程序: 2.程序结果:1)程序运行,我使用的测试数据如下

timerring的博客 5万+

为啥说SPFA已死?三种图论算法太难懂?深入浅出谈图论,看这篇文章就够了

为了给参赛者出难题,他们会计一些“恶意”图结构——例如具有大量负权边或复杂的环形结构,这些图会让 SPFA 的队列膨胀得离谱,从而使算法的最坏时间复杂度从 (O(V + E)) 升至 (O(VE))。Floyd-Warshall 算法和前面两位不同,它是个全局性算法,能够在 (O(V^3)) 的时间复杂度内解决任意两点之间的最短路径问题。这使得它在稠密图(边数远大于节点数)的情况下表现较好,尤其是在没有负权边的场景下,Dijkstra 是稳定且高效的选择。但在算法竞赛中,它的命运往往由出题人掌控。

qq_36916968的博客 3402
上一篇: 关于求最长路
下一篇: hdu1535 spfa
JustSteps
博客等级 码龄14年 48粉丝 210原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值