最短单源路径算法——SPFA

来源:互联网 发布:通达信手机炒股软件 编辑:程序博客网 时间:2024/06/10 06:09

引入

  毫无疑问,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数组计数,每次成员入队都将对应的值加一,如果某一点的入队次数超过了总点数减一,则肯定存在环,抛出异常信息。
  以上就是这期博客的全部内容了,希望大家能够有所收获,也欢迎私信博主交流学习。