透析SPFA算法(图例讲解)

article/2025/10/23 15:29:17

      

                   

      SPFA算法是Bellman-Ford的队列优化,所以先介绍Bellman-Ford算法。

       Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼(Richard Bellman, 动态规划的提出者)和小莱斯特•福特(Lester Ford)发明。

         Bellman-ford算法是求解连通带权图中单源最短路径的一种常用算法,它允许图中存在权值为负的边。 同时它还能够判断出图中是否存在一个权值之和为负的回路。如果存在的话,图中就不存在最短路径(因为,假设存在最短路径的话,那么我们只要将这条最短路径沿着权值为负的环路再绕一圈,那么这条最短路径的权值就会减少了,所以不存在最短的路径,因为路径的最小值为负无穷),如果不存在的话,那么求出源点到所有节点的最短路径。

 

Bellman-Ford算法的限制条件:

 要求图中不能包含权值总和为负值回路(负权值回路),如下图所示。


如果包含了负回路的话,0-1的最短距离可以无限-2+1-2+1...趋近负无穷

三、Bellman-Ford算法思想






考虑:为什么要循环V-1次?

答:因为最短路径肯定是个简单路径,不可能包含回路的,

如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径

如果回路的权值是负的,那么肯定没有解了

图有n个点,又不能有回路

所以最短路径最多n-1

又因为每次循环,至少松弛一边

所以最多n-1次就行了

 

介绍一下松弛计算


松弛计算之前,点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值(8)小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。

当然,如果出现一下情况


则不会修改点B的值,因为3+4>6。

 

Bellman-Ford算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。

该算法是利用动态规划的思想。该算法以自底向上的方式计算最短路径。
它首先计算最多一条边时的最短路径(对于所有顶点)。然后,计算最多两条边时的最短路径。外层循环需要执行|V|-1次。

例子

一下面的有向图为例:给定源顶点是0,初始化源顶点距离所有的顶点都是是无穷大的,除了源顶点本身。因为有5个顶点,
因此所有的边需要处理4次。 

 



按照以下的顺序处理所有的边:(B,E), (D,B), (B,D), (A,B), (A,C), (D,C), (B,C), (E,D).
第一次迭代得到如下的结果(第一行为初始化情况,最后一行为最终结果):

当 (B,E), (D,B), (B,D) 和 (A,B) 处理完后,得到的是第二行的结果。
当 (A,C) 处理完后,得到的是第三行的结果。
当 (D,C), (B,C) 和 (E,D) 处理完后,得到第四行的结果。


第一次迭代保证给所有最短路径最多只有1条边。当所有的边被第二次处理后,得到如下的结果(最后一行为最终结果): 


第二次迭代保证给所有最短路径最多只有2条边。我们还需要2次迭代(即所谓的松弛操作),就可以得到最终结果。 


 还有之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。
考虑如下的图:


经过第一次遍历后,点B的值变为5,点C的值变为8,这时,注意权重为-10的边,这条边的存在,导致点A的值变为-2。(8+ -10=-2)


第二次遍历后,点B的值变为3,点C变为6,点A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小。
 
在回过来看一下bellman-ford算法的第三部分,遍历所有边,检查是否存在d(v) > d (u) + w(u,v)。因为第二部分循环的次数是定长的,所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。比如


此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。

所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问。

 

Dijkstra算法与Bellman算法的区别

 Dijkstra算法和Bellman算法思想有很大的区别:
Dijkstra算法在求解过程中,源点到集合S内各顶点的最短路径一旦求出,则之后不变了,修改的仅仅是源点到T集合中各顶点的最短路径长度。

Bellman算法在求解过程中,每次循环都要修改所有顶点的dist[],也就是说源点到各顶点最短路径长度一直要到Bellman算法结束才确定下来。

如果存在从源点可达的负权值回路,则最短路径不存在,因为可以重复走这个回路,使得路径无穷小。

在Bellman算法中判断是否存在从源点可达的负权值回路的方法:

图的存储可以用邻接表或者邻接矩阵,当稠密图的时候邻接矩阵开不了那么大用邻接表,如果用vector做邻接表可可能会超时,所以最好用数组模拟建立邻接表


邻接表的建立:

首先用一个结构体E记录节点的信息,指向那个节点,以及指向节点的权值等信息,给E结构体设置一个next,让它指向H数组,H数组初始化为-1,初始化为-1是为了方便判断某个点直接相连点是否找完了

int H[N];  //存头节点
struct   //记录节点信息
{int v;int count;int next;
}E[N];
int T,n,m,top;
void Readmap(int m)  //读图
{memset(H,-1,sizeof(H));int top=0;for(int i=0;i<m;i++){scanf("%d%d%d",&x[i],&y[i],&c[i]);E[top].v=y[i];E[top].count=c[i];E[top].next=H[x[i]];H[x[i]]=top++;}
}




完整BF算法代码:

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 0x3f3f3f3f
#define N 1010
int nodenum, edgenum, original; //点,边,起点
typedef struct Edge //边
{int u, v;int cost;
} Edge;
Edge edge[N];
int dis[N], pre[N];
bool Bellman_Ford()
{for(int i = 1; i <= nodenum; ++i) //初始化dis[i] = (i == original ? 0 : MAX);for(int i = 1; i <= nodenum - 1; ++i)for(int j = 1; j <= edgenum; ++j)if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~){dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;pre[edge[j].v] = edge[j].u;}bool flag = 1; //判断是否含有负权回路for(int i = 1; i <= edgenum; ++i)if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost){flag = 0;break;}return flag;
}void print_path(int root) //打印最短路的路径(反向)
{while(root != pre[root]) //前驱{printf("%d-->", root);root = pre[root];}if(root == pre[root])printf("%d\n", root);
}int main()
{scanf("%d%d%d", &nodenum, &edgenum, &original);pre[original] = original;for(int i = 1; i <= edgenum; ++i){scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);}if(Bellman_Ford())for(int i = 1; i <= nodenum; ++i) //每个点最短路{printf("%d\n", dis[i]);printf("Path:");print_path(i);}elseprintf("have negative circle\n");return 0;
}


 

        由于Bellman-Ford的时间复杂度是O(VE)E为边的个数,这要比迪杰斯特拉算法慢(Dijksra的算法是一个贪婪算法,时间复杂度是O(VLogV)(使用最小堆) ),所以我们需要优化Bellman-Ford,于是引出来SPFA算法,在平均情况下,SPFA算法的期望时间复杂度为O(kE),k一般小于2,故O(E)

        在上面提供的BF算法核心的循环的提前跳出:在实际操作中,贝尔曼-福特算法经常会在未达到V-1次前就出解,V-1其实是最大值。于是可以在循环中设置判定,在某次循环不再进行松弛时,直接退出循环,进行负权环判定。

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

算法流程

算法大致流程是用一个队列来进行维护。初始时将源加入队列。每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。直到队列为空时算法结束。

这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法

SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单:

设Dist代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dist全部为+∞,只有Dist[S]=0,Fa全部为0。

维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。

每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于 Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环

SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右。

 

邻接表版SPFA:

long long SPFA(int st)
{for(int i=1;i<=n;i++)sp[i]=inf;sp[1]=0;queue<int> q;q.push(st);while(!q.empty()){int kai=q.front();q.pop();for(int i=H[kai];i!=-1;i=E[i].next){if(sp[E[i].v]>E[i].count+sp[kai]){sp[E[i].v]=E[i].count+sp[kai];q.push(E[i].v);}}}long long ans=0;for(int i=1;i<=n;i++)ans+=sp[i];return ans;
}


然后是邻接矩阵版本:其中used数组记录是否访问,pre数据记录路径

void spfa(int s,int dis[])
{int i,pre[N];bool used[N];queue<int> q;memset(used,0,sizeof(used));memset(pre,-1,sizeof(pre));for(i=0; i<N; i++)dis[i]=inf;dis[s]=0;used[s]=true;q.push(s);while(!q.empty()){int u=q.front();q.pop();used[u]=false;for(i=0; i<map[u].size(); i++){Node p=map[u][i];if(dis[p.v]>dis[u]+p.len){dis[p.v]=dis[u]+p.len;pre[p.v]=u;if(!used[p.v]){used[p.v]=true;q.push(p.v);}}}}
}

 

代码实现:

int used[Maxn],outqueue[Maxn],head[Maxn],low[Maxn],n,m;
struct Edge
{int to,w,next;
}edge[Maxm];
bool SPFA (int start)
{queue a;used[start] = 1;low[start] = 0;a.push(start);while (!a.empty()){int top = a.front();a.pop();outqueue[top]++;if (outqueue[top] > n) return false;for (int k = head[top]; k!= -1; k = edge[k].next){if (low[edge[k].to] > low[top] + edge[k].w)low[edge[k].to] = low[top] + edge[k].w;if (!used[edge[k].to]){used[edge[k].to] = 1;a.push(edge[k].to);}}}return true;
}int main()
{while (scanf ("%d%d", &n ,&m) != EOF){memset (used, 0 ,sizeof(used));memset (head, -1 ,sizeof(head));memset (outqueue, 0 ,sizeof(outqueue));memset (low, Max, sizeof(low));int k = 0;while (m--){int a,b,w;scanf ("%d%d%d", &a, &b, &w);edge[k].to = b;edge[k].w = w;edge[k].next = head[a];head[a] = k++;}if (SPFA(1))printf ("%d\n", low[n]);elseprintf ("不存在最短\n");}
}


SPFA在形式上和BFS非常类似,不同的是BFS中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本 身被改进,于是再次用来改进其它的点,这样反复迭代下去。

 但在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,


通常使用效率更加稳定的Dijkstra算法(无负边的时候用),有负边用SPFA。

 

推荐题目:

HDU1874

POJ3159

POJ2502

poj1511

以及其它最短路题目都可以采用SPFA算法

 

 

 

 

 

 

 

 

 


http://chatgpt.dhexx.cn/article/tkcOUGTE.shtml

相关文章

SPFA 算法详解( 强大图解,不会都难!)

适用范围&#xff1a;给定的图存在负权边&#xff0c;这时类似Dijkstra等算法便没有了用武之地&#xff0c;而Bellman-Ford算法的复杂度又过高&#xff0c;SPFA算法便派上用场了。 我们约定有向加权图G不存在负权回路&#xff0c;即最短路径一定存在。当然&#xff0c;我们可以…

OSPF NSSA区域路由的计算过程与FA值实验

概述&#xff1a; NSSA区域中不存在5类LSA&#xff0c;但是为了描述区域外的路由条目&#xff0c;则用7类LSA代替5类LSA。 7类LSA仅在NSSA区域中存在&#xff0c;当7类LSA离开NSSA区域进入普通区域时&#xff0c;可以转化为5类LSA。 NSSA-LSA报文格式和5类LSA相同&#xff1…

求不规则立方体表面积java_不规则立方体体积计算

展开全部 计算不规则的立体图形体积可62616964757a686964616fe78988e69d8331333431373866以把这个物体放入水中&#xff0c;用现在容积-未放入物体的容积就是体积或用放入物体后高-未放入物体*长*宽(1升1立方分米&#xff1b;1毫升1立方厘米)。 体积的国际单位制是立方米。一件…

冠层分析法(VCP)提取叶面积指数

文章目录 一、简介二、代码实现三、小结参考文献一、简介 该方法最早是由Omasa等人提出,是根据激光雷达光束与树木的接触频率来计算光束截获概率。总结来讲具体过程分为三步: 1、 从三维体素模型网格里判断是否有点,有点的网格标记为1,没点的网格标记为0,没点的网格表明冠…

SPFA例题

引入 SPFA的核心思想是优化dist[a]min(dist[a],dist[b]w)这个式子&#xff1b; 不难想到&#xff0c;如果我们想要优化dist[a]&#xff0c;那么只能从dist[b]w这个式子转移过来&#xff1b; 也就是说&#xff0c;只有dist[b]变小了&#xff0c;才有可能优化dist[a]&#xff…

OSPF中的SPF计算

OSPF域内路由计算&#xff1a;SPF计算 在1类LSA和2类LSA中&#xff0c;包括了拓扑信息和路由信息 Phase 1 &#xff1a;根据1类LSA中的P2P、TransNet、2类LSA&#xff0c;构建SPF树 Phase 2&#xff1a;根据1类LSA中的Stub、2类LSA&#xff0c;计算最优路由 左边是拓扑图&am…

聊一聊有限体积法

导读&#xff1a;有限体积法的工作原理。 一、求解对象 首先提一下我们的主角Navier-Stokes方程&#xff1a; 为了能够通过SIMPLE算法求解Navier-Stokes方程&#xff0c;我们需要将该方程改写为下列矩阵形式的方程&#xff1a; 其中为了系数矩阵&#xff0c;是每个网格未知…

IDEA中新导入的项目找不到maven project解决办法

刚导入的项目&#xff0c;导入进来之后&#xff0c;找不到maven project&#xff0c;idea打开view ->tool windows下也没有maven模块。查看了setting下plugins中maven插件也在。最后发现项目在导入过程中&#xff0c;因为选择的原因&#xff0c;没有指定位maven工程&#xf…

win10删除文件提示'找不到该项目'

win10系统下删除文件时出现了"找不到该项目"问题 遇到一个问题: win10系统下删除文件时出现了"找不到该项目"问题 百度搜索后得到 DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 这两行dos命令 创建(.txt)文件后写入这两行命令,保存,修改(.txt)至(.bat) 最后…

IDEA项目下不显示target目录或者target目录不完整没有新添加的资源

1.首先解决不显示target目录的问题 需要知道:如果不是maven工程,是没有target目录的,其次编译后才会生成target目录. 普通项目会生成out目录 a>如果设置过隐藏target目录 只需要找到settings-->Editor-->File Type-->Ignore Files and Folder删除该项 b>如果…

项目启动报错,找不到文件

java.io.FileNotFoundExceptionE:\software\apache-maven-3.6.1-bin\apache-maven-3.6.1\localrepository\xalan\xalan\2.7.0\xercesImpl.jar (系统找不到指定的文件。) java.io.FileNotFoundExceptionE:\software\apache-maven-3.6.1-bin\apache-maven-3.6.1\localrepository\…

idea项目类存在却找不到

这种情况&#xff0c;我真是奇了怪了&#xff0c;服了。我试了一下&#xff0c;试出了解决方案&#xff1a;将这个类移到同级另一个包&#xff0c;看见不报错了&#xff0c;再移回去。属于给idea虚晃一枪&#xff0c;再运行…emmmmm也还是报错的。于是不断的删iml文件&#xff…

Windows 删除”找不到该项目”文件夹

问题根源&#xff1a; 使用不可显示ASCII字符或采用UNICODE字符方法创建的文件或文件夹&#xff1b; 名称中含有..等特殊符号文件或文件夹名称不符合Windows命名规范或建立空格目录名创建的文件或文件夹&#xff1b; 使用下载工具创建的文件夹,在未下载完成前自行删除文件或…

idea找不到项目目录

有时候idea拉取项目时出现没有目录&#xff0c;src.main等等&#xff0c;下面就来带大家找到项目目录并显示出来 首先我们打开文件&#xff08;在左上角——找到项目结构&#xff09; 然后我们找到模块——点击左边那个加号,然后选择自己的项目目录。再选择一下jdk版本&#x…

【常见问题】vs 找不到导入的项目,请确认import声明

问题描述 当复制人家的vs项目文件到自己电脑上进行调试&#xff0c;重新生成解决方案失败&#xff0c;并报如下错误。 解决方法 1.分析报错内容&#xff0c;出现问题文件为vcxproj文件。 2.找到vcxproj文件&#xff0c;用记事本打开。 3.CTRLF搜索“Import”&#xff0c…

无法删除提示找不到该项目,该项目不在C:\users\?win10无法强力删除文件?

无法删除提示找不到该项目,该项目不在C:\users\?win10无法强力删除文件? 新建个TXT &#xff0c;把下边的代码粘贴过去&#xff1a; DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 保存之后把名字改成&#xff1a;Delete.bat 然后把要删除的文件夹拖到这个文件上&#xff0c;即可删…

git拉取项目报错:找不到项目 Repository not found

这段时间换了新公司&#xff0c;不想每天背着电脑跑来跑去了&#xff0c;就用了公司配发的电脑&#xff0c;前两天想用Git拉取公司的项目的时候却碰见问题了&#xff0c;无论是Git命令行拉取还是使用idea拉取&#xff0c;都会报错找不到项目 但是在浏览器使用链接是可以访问到项…

问题:找不到该项目,该项目不在指定目录下.......请确认位置......

参考网址&#xff1a;http://bbs.kafan.cn/thread-1398433-1-1.html 1.把下面的代码复制粘贴到一新建的txt记事本文档中&#xff0c;并另存为del.bat文件&#xff08;或者你喜欢的名字&#xff09;&#xff0c;注意扩展名为批处理文件bat&#xff1b; DEL /F /A /Q \\?\%1 RD …

windows删文件:找不到该项目,该项目不在xx中,请确认位置,然后重试 的解决方案

起因是手贱 打错了 搞得生成了一个 以.结尾的文件&#xff0c;删又删除不了&#xff0c;莫名其妙&#xff0c;然后 git push 也出问题&#xff1b; 删除就弹出类似下面的提示&#xff1b;删除上层文件夹也没用&#xff1b; 网上搜了一堆解决办法&#xff0c;说什么建立一个 ba…

找不到项目 该项不在计算机中,删除文件夹提示找不到该项目怎么删除?“找不到该项目”强删方法(图文)...

在使用电脑的过程中,尤其是办公,去创建一些文档,但是在删除的时候就删除不了了,系统提示“找不到该项目”,那么Win7删除文件夹找不到该项目怎么删除?下面云狐网分享一下删除文件或者文件夹提示“找不到该项目”强删方法,不会的来学习一下。uK5电脑_数码_手机应用问题解决…