SPFA算法

SPFA算法

解决问题

  • 单源点最短路径
  • 可处理负权边
  • 判断负环

Bellman-Ford算法

  • SPFA算法是Bellman-Ford的优化版本

  • Bellman-Ford算法流程

    • 初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;
    • 迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
    • 检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。
  • 算法伪码

for each vertex v∈V do
    dis[v] = ∞
dis[s] = 0
for i=1 to |v| - 1 do
    for each edge(u,v) ∈ E do
        if(dis[u] + w(u,v) < dis[v]) then
            dis[v] = dis[u] + w(u,v)
for each edge(u,v) ∈ E do
    if(dis[v] > dis[u] + w(u,v)) then
        return false
return true

  • 源码

#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);
        }
    else
        printf("have negative circle\n");
    return 0;
}
  • 主要思想

    • for i=1 to |v| - 1 相当于逐层生成最短路径树
    • 任意最短路径必然最多只包含|v|-1条边 只需循环这么多次
    • 事实上,每次实施一层松弛,经过i条边所到达的所有最短路径已经生成,不受后续松弛操作的影响。但是后续每次都还要判断
  • SPFA

    • 正是解决了Bellman-Fort算法在冗余松弛操作上的不足
    • 算法流程
      • 维护一个队列,每次进行一次类似BFS的操作实际上就是生成了该层的最短路径树
      • 每次取出队首结点u,并且用u点当前的最短路径估计值对u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

#include<iostream>
#include<queue>
#include<list>
#include<vector>
#include<cstring>
#include<set>
#include<stack>
#include<map>
#include<cmath>
#include<algorithm>
#include<string>
#include<stdio.h>
using namespace std;
typedef long long ll;
#define MS(x,i) memset(x,i,sizeof(x))
#define rep(i,s,e) for(int i=s; i<=e; i++)
#define sc(a) scanf("%d",&a)
#define scl(a) scanf("%lld",&a)
#define sc2(a,b) scanf("%d %d", &a, &b)
#define debug printf("debug......\n");
#define pfd(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
const double eps=1e-8;
const double PI = acos(-1.0);
const int inf = 0x3f3f3f3f;
const ll INF = 0x7fffffff;
const int maxn = 2e2+10;
int dx[4] = {0, 0, 1, -1};
int dy[4]  = {1, -1, 0 , 0};

const int M = 1e4+10;

int n,m;//顶点数边数
int head[maxn];//记录顶点的第一条边
int cnt; //记录当前是第几条边
//边
struct node{
    int to;
    int w;
    int nxt;
}edge[2*M]; //无向图的话要开两倍
//加边
void addEdge(int u, int v, int w){
    edge[cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].nxt = head[u];
    head[u] = cnt++;
}

bool vis[maxn];//记录是否在队列
int dis[maxn];//记录当前最短距离
int in[maxn];//记录结点被入队了多少次,如果大于n则说明有负环
//int path[maxn];//记录前驱
bool SPFA(int s){
    //初始化
    rep(i,1,n){
        vis[i] = 0;
        dis[i] = inf;
        in[i] = 0;
    }
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    dis[s] = 0;//到自己的距离为0
    in[s] = 1;
    while(!q.empty()){
        int fr = q.front();
        q.pop();
        vis[fr] = 0;//出队后置为0
        //对每个与fr相连的顶点 对其进行松弛操作
        for(int i=head[fr]; i!=0; i=edge[i].nxt){
            int v = edge[i].to;
            int w = edge[i].w;
            if(dis[v] > dis[fr] + w){
                //pre[v] = fr;
                dis[v] = dis[fr] +  w;
                if(!vis[v]){
                    q.push(v);
                    in[v]++;
                    vis[v] = 1;
                    if(in[v] > n) return false;
                }
            }
        }
    }
    return true;
}

int main(){
    while(cin>>n>>m){
        if(n==0 && m == 0) break;
        int u,v,w;
        cnt = 1;//第一条边
        MS(head , 0 );
        rep(i , 1, m){
            cin>>u>>v>>w;
            addEdge(u , v , w);
            addEdge(v , u , w);

        }
        int s;
        s = 1;
        if(SPFA(s)) cout<<dis[n]<<endl;
        else cout<<"-1"<<endl;
    }
    return 0;
}

posted @ 2019-04-16 13:01  西风show码  阅读(196)  评论(0编辑  收藏  举报