最短路径(3)
最短路径算法(3)
1.dijkstra的局限性
之前在dijkstra的证明当中,我们认为所有的边权为正,但如果为负是否还是正确的呢?
显然这是错误的。
我们认为每次迭代的离源点最近的路为最短路。这是因为之后的路不会更短了,毕竟后面的路都是正的。路径只会变长。
但是如果存在负的边,这就不行了,之后的路会出现更短的路。
这时该怎么办呢?
一个很自然的想法:把每条边的都加上一个数字,使得每条边都为正。接下来再进行dijkstra。这样可以吗?
在经过实践之后发现这是不行的。
2.bellman-ford算法
bellman算法可以解决负权问题。
在此之前,我们思考一个问题:
一个图什么时候没有最短路?
显然如果每一条边都为正,一定会有最短路。
如果有负的呢?
当出现负环时,我们可以一直在这个圈上打转,路径长度也会一直变小,所以有负环时没有最短路。
同理我们可以得到有正环时没有最长路。
接下来,开始介绍bellman—ford算法。
在dijkstra中用到了这样的操作:
dis[v] = min(dis[v],dis[u]+wei[u][v]);
其中dis[v]是点v到源点的距离,dis[u]是u到源点的距离,wei[u][v]是(u,v)边的长度
就是说,如果我们可否绕过u点使得dis[v]变得更松,更短。所以我们要找所有的边,看哪些边可以被松弛。
显然,只需要经过若干次松弛便可获得最短路,但问题在于——
多少次?
如果我们进行了k轮松弛操作(这里的松弛指松弛所有边)
那么有递推公式:
dis[k][u] = min(dis[k-1][u],dis[k-1][v]+wei[v][u]);
和folyd一样,这也是DP的思想。(bellman本人就是DP思想的提出者。)
那么k最多可以取到多少呢?
源点到达一个点最多需要n-1条边,所以我们最多也只需要进行n-1轮松弛
主算法代码极为简洁:
for:i 0~n-1
for each edge(u,v) of G
dis[v] = min(dis[v],wei[u][v]+dis[u]);
但是这样的算法所花费的时间可是O(nm)的。这似乎是不够好的了。
3.SPFA
bellman的缺陷在于,花了太多的时间在不必要的松弛上了。
现在回到了之前的那个问题——
多少次?
使用队列代替循环检查,可以在大部分情况下得到优化。当然也可以使用其他的数据结构和方法来优化,例如栈,栈在判断负圈的过程中可能会更加好。
SPFA算法高效的解决了这个问题的大部分情况。但很可惜的是它并不稳定。
"用一个队列来进行维护。初始时将源加入队列。每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。直到队列为空时算法结束"
queue Q;//队列维护
Q.push(s);//加入源点
while(!Q.empty()){
u = Q.front();Q.pop();//队头
Q.push();
for each (u,v) of G,vis[v] = 1//遍历邻居 ,v不在队列中
if(s->u > s->v + v->u){//松弛成功
s->u = s->v + v->u;
Q.push(v);
}
}
4.代码实现
直接上SPFA了
int dis[N],nxt[N],head[N];
queue<int> Q;
int tot = 0;
struct Edge{
int to,w;
}edge[N];
//下面是前向星
/*void add(int u,int v,int x){
nxt[++tot] = head[u];
head[u] = tot;
edge[tot].w = x;
edge[tot].to = v;
}*/
bool vis[N] = {false};
void spfa(int s){
Q.push(s);
vis[s] = true;
while(!Q.empty()){
int u = Q.front();Q.pop();
for(int i = head[u];i;i = nxt[i]){
int v = edge[i].to;
if(dis[v] > dis[u]+edge[i].w){
dis[v] = dis[u]+edge[i].w;
if(!vis[v]) vis[v]=true,q.push(v);
}
}
}
}

浙公网安备 33010602011771号