最短单源路径算法——SPFA

引入

  毫无疑问,Dijkstra因为其简单易懂、实用可靠及不错的时间复杂度受到OI初学者的青睐,但它有一个致命缺点:无法处理边权值为负的情况。
  以下是博主一段优先队列优化的Dijkstra算法。
  

for(int i=1;i<city;i++)
    {
        int nextnode,nextmin;
        int numnoww;
        while(true)
        {
            numnoww=minnow.size();
            if(numnoww==0)break;//空队列防止RE
            x=minnow.top();
            minnow.pop();
            if(!vis[x.dot])//未访问过的才被选出,否则continue
            {
                nextnode=x.dot;
                nextmin=x.len;
                break;
            }
        }
        if(numnoww==0)break;
        vis[nextnode]=true;
        for(int j=data[nextnode].size()-1;j>=0;j--)
        {
            int t=data[nextnode][j].dot;
            if(!vis[t]&&dist[t]>dist[nextnode]+data[nextnode][j].len)
            {
                dist[t]=dist[nextnode]+data[nextnode][j].len;
                //修改最优距离
                x.dot=t;
                x.len=dist[t];
                minnow.push(x);//入队
            }
        }
    }

  显然这种方法中访问一遍某个节点后该节点就被标记为已访问了。而当存在负权值边时,可能存在在访问另一负权端点时能将已访问过的端点继续优化,但因已经将vis数组修改为true而无法更新的情况。所以如果用标准Dijkstra算法做含有负权值边的问题纯粹是碰运气。
  此时我们可以用SPFA算法解决该问题。SPFA算法是1994年西安交通大学段凡丁提出的Bellman Ford算法的队列优化版本,其时间复杂度为O(ME)。(看了很多算法都是外国人提出的,国人创造的算法很自豪有木有(っ•̀ω•́)っ )。

SPFA算法主要思想

1.将除起点的其他点的距离(dist)初始化为INF,将起点初始为0,保证后续步骤能优化至最优。
2.先将起点push入队列,队首元素出列优化与起点相连的所有点的dist值。若比之前的dist值小且当前队列里没有该点则将该点push入队列。
3.重复出队队首元素,优化与之相连的点,直至队列中没有元素,则所有点到起点的距离已优化至最优。

  显然SPFA中相同点可以多次加入队列,保证了有负权边存在的情况下dist的最优值。
  博主以以下题目为例题:
  http://ybt.ssoier.cn:8088/problem_show.php?pid=1379
  为了实现这一操作,我们需要如下变量:
  

queue <int> next;//算法核心,用来存储下一步搜索节点;手写队列当然也可以
int dist[2505];//储存起点到某个点的距离;
bool now[2505];//检测当前队列中是否有某个点存在;
struct sd
{
    int nextnode;//保存可以到的点
    int len;//保存到此点的距离
};
sd x;//方便进行push操作
vector <sd> data[2505];//保存各点数据

贴上主程序代码,讲解见注释:

//
int main()
{
    memset(now,false,sizeof(now));//初始化为未访问
    memset(dist,127,sizeof(dist));
    //初始化为很大的数
    int city,road,start,endd,a,b,c,noww,p,v;
    scanf("%d%d%d%d",&city,&road,&start,&endd);
    for(int i=1;i<=road;i++) //记录各点路径数据
    {
        scanf("%d%d%d",&a,&b,&c);
        x.nextnode=b;
        x.len=c;
        data[a].push_back(x);
        x.nextnode=a;
        data[b].push_back(x);
    }
    next.push(start);//将起点push入队列
    now[start]=true;
    dist[start]=0;//将自己到自己的距离改为0
    while(!next.empty())
    {
        noww=next.front();//出队
        next.pop();
        now[noww]=false; //注意出队后要还原为false
        for(int i=data[noww].size()-1;i>=0;i--)
        {
            p=data[noww][i].nextnode;
            v=data[noww][i].len;
            if(dist[p]>dist[noww]+v)//可优化
            {
                dist[p]=dist[noww]+v;
                if(!now[p])//不在队列中则加入队列
                {
                    now[p]=true;
                    next.push(p);
                }
            }
        }
    }
    printf("%d",dist[endd]);
    return 0;
}

  注:显然SPFA算法无法处理带有负权值环的图的最短路问题(死循环,一直在负权值环里搜索直到地老天荒…),但可以判断图中是否存在负权值环。只需再加入一个num数组计数,每次成员入队都将对应的值加一,如果某一点的入队次数超过了总点数减一,则肯定存在环,抛出异常信息。
  以上就是这期博客的全部内容了,希望大家能够有所收获,也欢迎私信博主交流学习。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值