最短路径(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); 
                   }
           }
        }
    }
posted @ 2020-12-29 20:45  Paranoid5  阅读(99)  评论(0)    收藏  举报