Bellman算法和SPFA算法

简介: Bellman算法和SPFA算法

Dijkstra算法比较快速,但是如果遇到负边就无能为力了,而Bellman算法可以解决负边问题,只要不是负环。

这个算法数据结构没有讲过,他主要是每次对所以边进行松弛操作,进行n-1次得到最短路径。

其原理如下所述:

首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|n-1条边。

其次,从源点s可达的所有顶点如果存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。

在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。因为最短路径最多只包含|v|-1 条边,所以,只需要循环|v|-1 次。

如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 d[v]仍保持 +∞,则表明从s到v不可达。

如果有负权回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。


根据这个原理写出代码如下:

bool Bellman(int s,int n){
  fill(dis,dis+n,inf);
  dis[s]=0;
  for(int i=0;i<n-1;i++){//n-1此松弛操作 
      int flag=1;
    for(int u=0;u<n;u++) 
    {
      for(int j=0;j<Adj[u].size();j++)
      {
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x])
       {dis[x]=dis[u]+Adj[u][j].weight;//松弛操作  
       flag=0;
      } 
      }
    }
    if(flag)return true;//如果这一轮没有被松弛,直接退出 
      }
  for(int u=0;u<n;u++) //判断是否存在负环 
    {
      for(int j=0;j<Adj[u].size();j++)
      {
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x])
        return false;//还能松弛存在负环,失败 
      } 
    }
    return true;  
  }

虽然理解简单但是这个算法耗时较大,每一轮都要遍历所有边,其实由于一个顶点u的d[u]改变时,以此点出发的邻接点d[v]才可能改变,结合Bellman树的形成过程可以类比层次遍历,用一个队列存放结点,每拿出一个结点对邻接点松弛,如果能松弛且不在队列里就让其入队,直到队空(说明无负环,)或者某个结点入队超过n-1次,说明有负环。这就是SPFA算法,一般比Dijkstra算法还要快,比较高效。代码如下:

bool SPFA(int s,int n){
    fill(inq,inq+n,false);//记录是否入队
    fill(num,num+n,0);//记录入队次数
    fill(dis,dis+n,inf);
    queue<int>q;
    q.push(s);
    inq[s]=true;
    num[s]++;
    dis[s]=0;
    while(!q.empty()){
      int u=q.front();q.pop();
      inq[u]=false;
      for(int j=0;j<Adj[u].size();j++){
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x]){
        dis[x]=dis[u]+Adj[u][j].weight;
        if(!inq[x])
        {
          q.push(x);
          num[x]++;
          inq[x]=true;
          if(num[x]>n-1)//负环
          return false;
        }
      }
      }   
    }
  return true;  
  }

此外关于最短路径还有一个Floyd算法,这里打包带走,下面是三个算法的全体代码:

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int maxv=101;
const int inf=10000000;
struct node{
  int v,weight;
};
vector<node>Adj[maxv] ;
int dis[101];
//bellman算法 
bool Bellman(int s,int n){
  fill(dis,dis+n,inf);
  dis[s]=0;
  for(int i=0;i<n-1;i++){//n-1此松弛操作 
      int flag=1;
    for(int u=0;u<n;u++) 
    {
      for(int j=0;j<Adj[u].size();j++)
      {
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x])
       {dis[x]=dis[u]+Adj[u][j].weight;//松弛操作  
       flag=0;
      } 
      }
    }
    if(flag)return true;//如果这一轮没有被松弛,直接退出 
      }
  for(int u=0;u<n;u++) //判断是否存在负环 
    {
      for(int j=0;j<Adj[u].size();j++)
      {
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x])
        return false;//还能松弛存在负环,失败 
      } 
    }
    return true;  
  }
  //SPFA算法
  bool inq[maxv] ;
  int num[maxv];
  bool SPFA(int s,int n){
    fill(inq,inq+n,false);
    fill(num,num+n,0);
    fill(dis,dis+n,inf);
    queue<int>q;
    q.push(s);
    inq[s]=true;
    num[s]++;
    dis[s]=0;
    while(!q.empty()){
      int u=q.front();q.pop();
      inq[u]=false;
      for(int j=0;j<Adj[u].size();j++){
        int x=Adj[u][j].v;
      if(dis[u]+Adj[u][j].weight<dis[x]){
        dis[x]=dis[u]+Adj[u][j].weight;
        if(!inq[x])
        {
          q.push(x);
          num[x]++;
          inq[x]=true;
          if(num[x]>n-1)
          return false;
        }
      }
      }   
    }
  return true;  
  }
//floyd算法
int d[maxv][maxv] ;
void floyd(int n){
  for(int k=0;k<n;k++)
  for(int i=0;i<n;i++)
  for(int j=0;j<n;j++)
  if(d[i][k]!=inf&&d[k][j]!=inf&&d[i][k]+d[k][j]<d[i][j])
  d[i][j]=d[i][k]+d[k][j];
}
  int main(){
    int n,m,s,t;
  int x,y,w;
  node temp;
  scanf("%d%d%d%d",&n,&m,&s,&t);
  for(int i=0;i<m;i++){
    scanf("%d%d%d",&x,&y,&w); 
    temp.weight=w;
    temp.v=x;
    Adj[y].push_back(temp);
    temp.v=y;
    Adj[x].push_back(temp);
  }
  if(SPFA(s,n)) {
    for(int i=0;i<n;i++)
    printf("%d ",dis[i]);
  }
    return 0;
  } 
相关文章
|
6月前
|
算法
class065 A星、Floyd、Bellman-Ford与SPFA【算法】
class065 A星、Floyd、Bellman-Ford与SPFA【算法】
47 0
|
存储 算法 数据建模
【最短路算法】SPFA
【最短路算法】SPFA
98 0
|
算法
SPFA算法-最短路-负环
SPFA算法-最短路-负环
84 0
|
6月前
|
存储 算法
最短路之SPFA算法
最短路之SPFA算法
55 0
|
存储 算法
最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)
最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)
178 0
|
算法 Java
SPFA 算法:实现原理及其应用
SPFA算法,全称为Shortest Path Faster Algorithm,是求解单源最短路径问题的一种常用算法,它可以处理有向图或者无向图,边权可以是正数、负数,但是不能有负环。 首先我们需要起点s到其他顶点的距离初始化为一个很大的值(比如9999999,像是 JAVA 中可以设置 Integer.MAX_VALUE 来使),并将起点s的距离初始化为0。同时,我们还需要将起点s入队。
323 1
|
存储 算法
搜索与图论 - spfa 算法
搜索与图论 - spfa 算法
|
存储 算法
最短路径——Bellman-Ford算法以及SPFA算法
最短路径——Bellman-Ford算法以及SPFA算法
最短路径——Bellman-Ford算法以及SPFA算法
|
存储 算法
spfa算法的实现
spfa算法的实现
|
算法 Java
蓝桥杯最短路(java过)&&spfa单源最短路算法
百度百科上spfa的思路为:动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
133 0
蓝桥杯最短路(java过)&&spfa单源最短路算法