第四章 图(八) - 《算法》读书笔记

第四章 图(八)

4.4.5 无环加权有向图中的最短路径算法

按照拓扑排序顺序放松顶点,就能在和E+V成正比的时间内解决无环加权有向图的单点最短路径问题。

  • 算法特点:
    • 能够在线性时间内解决单点最短路径问题
    • 能够处理负权重的边
    • 能够解决相关的问题,例如找出最长的路径

4.4.6 一般加权有向图中的最短路径问题

4.4.6.2 尝试II

  • Dijkstra算法根据距离起点的远近一次检查路径,但这是基于添加一条边会使路径变得更长的假设

4.4.6.3 负权重的环

定义:加权有向图中的负权重环是一个总权重(环上所有边的权重之和)为负的有向环。

当且仅当加权有向图中至少存在一条从s到v的有向路径所有从s到v的有向路径上的任意顶点都不存在于任何负权重环中时,s到v的最短路径才是存在的。

4.4.6.4 尝试III

  • 一个定义明确且可以解决加权有向图最短路径问题的算法要能够:
    • 对于从起点不可达的顶点,最短路径为正无穷
    • 对于从起点可达但路径上的某个顶点属于一个负权重环的顶点,最短路径为负无穷
    • 对于其他所有顶点,计算最短路径的权重(以及最短路径树)

Bellman-Ford算法:在任意含有V个顶点的加权有向图中给定起点s,从s无法到达任何负权重环,以下算法能够解决其中的单点最短路径问题:将distTo[s]初始化为0,其他distTo[]元素初始化为无穷大。以任意顺序放松有向图的所有边,重复V轮。

Bellman-Ford算法所需的时间和EV成正比,空间和V成正比

4.4.6.5 基于队列的Bellman-Ford算法

  • 使用FIFO队列记录上一轮distTo[]发生变化的顶点,只有从这些顶点指出的边才能够改变其他distTo[]元素的值

4.4.6.6 实现

  • 具体实现如下:
public class BellmanFordSP{
    private double[] distTo;
    private DirectedEdge[] edgeTo;
    private boolean[] onQ;//该顶点是否存在于队列中
    private Queue<Integer> queue;
    private int cost;//relax()的调用次数
    private Iterable<DirectedEdge> cycle;
    
    public BellmanFordSP(EdgeWeightedDigraph G, int s){
        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];
        onQ = new boolean[G.V()];
        queue = new Queue<Integer>();
        for(int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTi[s] = 0.0;
        queue.enqueue(s);
        onQ[s] = true;
        while(!queue.isEmpty() && !hasNegativeCycle()){
            int v = queue.dequeue();
            onQ[v] = false;
            relax(G, v);
        }
    }
    private void relax(EdgeWeightedDigraph G, int v){
        for(DirectedEdge e : G.adj(v)){
            int w = e.to();
            if(distTo[w] > distTo[v] + e.weight()){
                distTo[w] = distTo[v] + e.weight();
                edgeTo[w] = e;
                if(!onQ[w]){
                    queue.enqueue(w);
                    onQ[w] = true;
                }
            }
            if(cost++ % G.V() == 0)
                findNegativeCycle();
        }
    }
}
//负权重环相关方法见后面章节
private void findNegativeCycle();
public boolean hasNegativeCycle();
public Iterable<Edge> negativeCycle();

对于任意含有V个顶点的加权有向图和给定的起点s,在最坏情况下,基于队列的Bellman-Ford算法解决最短路径问题(或者找到从s可达的负权重环)所需的时间与EV成正比,空间和V成正比。

4.4.6.8 负权重环的检测

  • 查找负权重环的实现如下所示:
private void findNegativeCycle(){
    int V = edgeTo.length;
    EdgeWeightedDigraph spt;
    spt = new EdgeWeightedDigraph(V);
    for(int v = 0; v < V; v++)
        if(edgeTo[v] != null)
            spt.addEdge(edgeTo[v]);
    EdgeWeightedCycleFinder cf;
    cf = new EdgeWeightedCycleFinder(spt);
    cycle = cf.cycle();
}
  • 当所有边放松V轮之后,当前仅当队列非空时有向图中才存在从起点可达的负权重环
  • 如果是这样,edgeTo[]数组所表示的子图中必然含有这个负权重环
posted @ 2021-02-19 20:30  一天到晚睡觉的鱼  阅读(76)  评论(0)    收藏  举报