SPFA-hdu4076

40 篇文章 0 订阅
2 篇文章 0 订阅

SPFA

SPFA(Shortest Path Faster Algorithm)算法,是西南交通大学段凡丁于 1994 年发表的,其在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

SPFA和Bellman-ford一般都是用来处理带负权的最短路问题(Dijkstra不能处理带负权回路的最短路),而SPFA是在Bellman-ford的基础上优化的,所以我先介绍一下Bellman-ford算法的思想。

Bellman-ford思想
设顶点数为 n,边数为 m,源点为 source,数组dist[i]记录从源点 source 到顶点 i 的最短路径,除了dist[source]初始化为 0 外,其它dist[]皆初始化为 MAX。
然后循环 n-1 次,每次循环遍历所有的边e(u,v),然后进行松弛操作(就是dist[u] + w(u, v) < dist[v],则使 dist[v] = dist[u] + w(u, v),其中 w(u, v) 为边 e(u, v) 的权值),n-1次循环后从源点到其它所有点的最短路径就确定了。
那么怎么判断负权?想一想,本来经过n-1次循环,所有点的最短路径应该已经确定。但是如果存在负权回路,那么可以不断在负权回路那里循环,不断减小(其实就是最短路可以无限小)。所以我们进行第n次循环,如果还能进行松弛操作,说明带负权回路,不能说明不带。

SPFA思想
SPFA就是用一个队列来优化Bellman-ford。
设立一个队列用来保存待优化的节点,优化时每次取出队首节点 u,并且用 u 点当前的最短路径估计值dist[u]对与 u 点邻接的顶点 v 进行松弛操作,如果 v 点的最短路径估计值dist[v]可以更小,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出顶点来进行松弛操作,直至队列空为止。
SPFA怎么判断负权回路?如果某个点进入队列的次数大于等于 n,则存在负权回路,其中 n 为图的顶点数。因为即使一个点与其它所有点都存在路径,每次都能放入队列,最多也就是n-1次,多的话肯定是存在负权回路使它能不断进入负权回路。

对于最短路问题,个人觉得如果不带负权的话最好用邻接表+最小堆优化(优先队列优化)的Dijkstra(边数少也可考虑用SPFA),复杂度适宜,O((n+m) logn ),也稳定。如果带负权回路就用SPFA,时间复杂度(最好O(m),最差O(n*m)),不稳定。

hdu4076

具体看代码注释

#include <bits/stdc++.h>
using namespace std;

#define INF 0x3f3f3f3f

int w,h;
int dis[4][2]={-1,0,0,-1,1,0,0,1};//上下左右走
struct node{
    int x,y;
};
int matrix[35][35],dist[35][35],vis[35][35],nx[35][35],ny[35][35];
//matrix保存地图状态,0是草,-1是墓碑,1是洞
//dist[i][j]保存到i,j的最短时间,vis保存访问状态
//nx,ny保存洞的终点坐标
int t[35][35],inqueue_num[35][35];
//t保存洞的用时,inqueue_num保存入队次数

bool SPFA()
{
    bool flag=true;
    node p,next;
    queue<node>q;
    //起点入队
    p.x=p.y=0;
    dist[0][0]=0;
    vis[0][0]=1;
    inqueue_num[0][0]++;
    q.push(p);
    while(!q.empty())
    {
        p = q.front(),q.pop();
        vis[p.x][p.y]=0;
        if(p.x==w-1&&p.y==h-1) continue;  //到达终点之后就不用再入队松弛了
        if(matrix[p.x][p.y])  //有洞
        {
            next.x = nx[p.x][p.y];
            next.y = ny[p.x][p.y];
            if(dist[next.x][next.y]>dist[p.x][p.y]+t[p.x][p.y])  //如果能更新
            {
                dist[next.x][next.y]=dist[p.x][p.y]+t[p.x][p.y];  //松弛
                if(!vis[next.x][next.y])
                {
                    q.push(next);
                    inqueue_num[next.x][next.y]++;
                    if(inqueue_num[next.x][next.y]>=w*h) return flag=false;  //存在负权回路
                    vis[next.x][next.y]=1;
                }
            }
            continue;  //有洞必须到洞的终点
        }
        for(int i=0;i<4;i++)  //上下左右遍历
        {
            next.x = p.x+dis[i][0];
            next.y = p.y+dis[i][1];
            //超出地图范围或有墓碑
            if(next.x<0 || next.x>w-1 || next.y<0 || next.y>h-1 || matrix[next.x][next.y]==-1) continue;
            if(dist[next.x][next.y]>dist[p.x][p.y]+1)
            {
                dist[next.x][next.y]=dist[p.x][p.y]+1;
                if(!vis[next.x][next.y])
                {
                    q.push(next);
                    inqueue_num[next.x][next.y]++;
                    if(inqueue_num[next.x][next.y]>=w*h) return flag=false;
                    vis[next.x][next.y]=1;
                }
            }
        }
    }
    return flag;
}

int main()
{
    while(~scanf("%d%d",&w,&h)&&w!=0&&h!=0)
    {
        int num,x1,x2,y1,y2,tmp;
        memset(matrix,0,sizeof(matrix));
        memset(dist,INF,sizeof(dist));
        memset(vis,0,sizeof(vis));
        memset(inqueue_num,0,sizeof(inqueue_num));
        scanf("%d",&num);
        while(num--)
        {
            scanf("%d%d",&x1,&y1);
            matrix[x1][y1] = -1;
        }
        scanf("%d",&num);
        while(num--)
        {
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&tmp);
            matrix[x1][y1]=1;
            nx[x1][y1]=x2,ny[x1][y1]=y2;
            t[x1][y1]=tmp;
        }
        if(SPFA())
        {
            if(dist[w-1][h-1]==INF) printf("Impossible\n"); //不能到达终点
            else printf("%d\n",dist[w-1][h-1]);
        }
        else printf("Never\n");  //有负权回路
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值