Bellman-Ford算法

参考:
Single-Source Shortest Path:Bellman-Ford Algorithm
SPFA——基于Bellman-Ford的队列优化
How is the Bellman Ford algorithm a case of dynamic programming?
Shortest Path and Dynamic Programming Algorithms

算法简介

Bellman-Ford算法可以用于含有负权重的有向图中单源最短路径计算。他的本质是一种暴力求解,同时也运用了动态规划的思想。

算法思想

Bellman-Ford算法的思路是对于有N个节点的图,给定起点s,只要通过N-1轮更新(松弛操作),我们必定可以得到任意点到s点的最小代价dis。具体的证明在《算法》(第四版)里面有介绍。简要叙述如下:

1.将所有节点分为两类:已知最短距离的节点和剩余节点。
2.这两类节点满足这样的性质:已知最短距离的节点的最短距离值都比剩余节点的最短路值小。(这一点也和dijkstra一样)
3.有了上面两点说明,易知到剩余节点的路径一定会经过已知节点
4.而从已知节点连到剩余节点的所有边中的最小的那个边,这条边所更新后的剩余节点就一定是确定的最短距离,从而就多找到了一个能确定最短距离的节点,不用知道它到底是哪个节点。

因此,依据上面的结论,对于N点的图,我们只要循环N-1轮,每轮都依次更新N个点的dis,就可以在结束时得到所有点到s的最小的dis[]。更新的操作和Dijkstra算法很像,对于当前点node,遍历其所能连接的邻接点adjNode,由于已知dis[node],并且知道cost(node2adjNode),那么就可以计算出一个新的到达adjNode的代价,即:tempDis = dis[node] + cost(node2adjNode),通过比较tempDis和dis[adjNode]的值,就可以判断是否需要更新dis[adjNode]的值了,若tempDis较小,则更新,并且将adjNode的父节点设为node。
进一步的,由结论可知,经过N-1轮即可找到最小dis,那如果我们再进行一轮循环,如果此时仍然由更小的dis出现,那么就可以断定存在负权重环了。

算法伪代码

Bellman_Ford(graph, sNode, eNode){
    set dis[] = infinite;
    dis[sNode] = 0;
    for(i=1->N-1){                  //对N个点重复更新N-1次
        for(node : graph){          //遍历图中所有点
            adjNodeList = getAdjacentNode(node);
            for(adjNode : adjNodeList){         //更新当前节点node的所有邻接点,即当前点的边,故两个内部的for循环次数为总边数E
                dis[adjNode] = min(dis[adjNode], dis[node]+cost(node2adjNode));     //典型的DP关系式
                if(dis[adjNode] > (dis[node]+cost(node2adjNode)))
                    adjNode.father = node;
            }
        }  
    }
    //负权重环检查,即再多循环一次
    for(node : graph){          
    adjNodeList = getAdjacentNode(node);
        for(adjNode : adjNodeList){        
            if(dis[adjNode] > (dis[node]+cost(node2adjNode)))
                error "graph include negative cycle!"
        }
    }  
    getTrace(eNode);
}

关于动态规划的思考

从上面伪代码,我们可以看出Bellman-Ford算法其实属于一种动态规划,因为他有循环,当前循环会比较上一轮循环的结果和当前方案的结果,并从中选择更优的作为本轮循环的结果。在考虑之前的Dijkstra算法,其实他也是用到了动态规划的思想,即通过比较当前轮的方案和上一轮的结果来决定选择哪个作为当前轮的结果。不过考虑到Dijkstra算法采用了队列而不是for循环的形式,因此我们更倾向于其使用的是贪心思想。下面将讲到将队列的数据结构用于Bellman-Ford算法之中来改善其性能。

优化-基于队列的Bellman-Ford算法(SPFA)

考虑初始的Bellman-Ford算法,在刚开始的时候,其是很多节点的dis都是无穷大的,特别是远离起点s的节点,因此前面几次的for(i=1->N-1)循环的效率是非常低的。为了跳过哪些无穷大的节点,或者已经是最小dis的点。我们可以采用队列的形式,将变化的节点加入队列去继续更新。即类似Dijkstra算法,维护一个openList队列,对于每次得到的节点node,将其邻接点放入队列中,然后下次依然从队列中取节点,直到队列为空。关于采用队列的方法时,如何判断是否有负权重环呢?我们可以进行统计,统计节点出现的次数,如果出现过n次,则必然存在负权重环。

基于队列的Bellman-Ford算法(SPFA)的伪代码

Bellman_FordWithQueue(graph, sNode, eNode){
    set dis[] = infinite;
    dis[sNode] = 0;
    openList.add(sNode);
    isOnQueue[sNode] = true;
    while(!openList.isEmpty()){
        node = openList.getFirstNode();
        isOnQueue[node] = false;
        for(node : graph){
            adjNodeList = getAdjacentNode(node);
            for(adjNode : adjNodeList){         
                if(dis[adjNode] > (dis[node]+cost(node2adjNode))){
                    adjNode.father = node;
                    dis[adjNode] =  dis[node]+cost(node2adjNode);
                    if(isOnQueue[adjNode]==false){
                        openList.add(adjNode);
                        if(++count[adjNode] > N)
                            error "graph include negative cycle!"
                    }
                }
            }
        }  


}

Dijkstra算法和SPFA算法的区别

参考:
Dijkstra、Bellman-Ford及Spfa算法思想对比
ACM-最短路(SPFA,Dijkstra,Floyd)之最短路——hdu2544

总的来说就是SPFA适合带负权重边的稀疏图,在遇到稠密图时,性能会退化为Bellman-Ford算法的O(VE)。Dijkstra算法适合于无负权重边的有向图。
当两个算法都用于非负权重边的稀疏图时,从代码分析可以看出,Dijkstra算法中的openList保存的点一旦出了列表就不会再次进入,而SPFA算法中的openList中的节点则有可能再次进入,因此性能也会有所差距。

猜你喜欢

转载自blog.csdn.net/timemagician/article/details/80859248