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