算法(第4版) Chapter 4.4 最短路徑

Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路雲
Chapter 4 Section 4 最短路徑算法


基本假設

  • 圖是強連通的數組

  • 權重都爲正數據結構

  • 最短路徑不必定是惟一的,咱們只找出其中一條性能

  • 可能存在平行邊和自環(但咱們會忽略自環)this

數據結構

加權有向邊API

加權有向邊API

  • 有向邊,因此新增方法from() 和 to()spa

DirectedEdge 代碼

public class DirectedEdge {
    private final int v; // edge source
    private final int w; // edge target
    private final double weight; // edge weight

    public DirectedEdge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    public double weight() {
        return weight;
    }

    public int from() {
        return v;
    }

    public int to() {
        return w;
    }

    public String toString() {
        return String.format("%d->%d %.2f", v, w, weight);
    }
}

加權有向圖API

加權有向圖API

EdgeWeightedDigraph 代碼

public class EdgeWeightedDigraph {
    private final int V; // number of vertices
    private int E; // number of edges
    private Bag<DirectedEdge>[] adj; // adjacency lists

    public EdgeWeightedDigraph(int V) {
        this.V = V;
        this.E = 0;
        adj = (Bag<DirectedEdge>[]) new Bag[V];
        for (int v = 0; v < V; v++)
            adj[v] = new Bag<DirectedEdge>();
    }

    public EdgeWeightedDigraph(In in)// See Exercise 4.4.2.
    
    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    public void addEdge(DirectedEdge e) {
        adj[e.from()].add(e);
        E++;
    }

    public Iterable<Edge> adj(int v) {
        return adj[v];
    }

    public Iterable<DirectedEdge> edges() {
        Bag<DirectedEdge> bag = new Bag<DirectedEdge>();
        for (int v = 0; v < V; v++)
            for (DirectedEdge e : adj[v])
                bag.add(e);
    }
}

最短路徑API

最短路徑API

邊的鬆弛

兩條路徑code

  1. s --> worm

  2. s --> v , v -> w隊列

比較哪一條路徑更短,記錄更短的那個邊。圖片

  • 若 路徑1 < 路徑2,原路徑 s --> w 已經最短,不更新。

    • 邊 v -> w 失效

  • 若 路徑1 > 路徑2,新路徑 s --> v , v -> w 更短,更新,放鬆成功

    • 路徑 s --> w 中 原指向w的那一條邊失效

private void relax(DirectedEdge e) {
    int v = e.from(), w = e.to();
    if (distTo[w] > distTo[v] + e.weight()) {
        distTo[w] = distTo[v] + e.weight();
        edgeTo[w] = e;//記錄的是邊,而不是點
    }
}

頂點的鬆弛

頂點的鬆弛

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;
        }
    }
}

最短路徑算法的理論基礎

最優性條件

  • 當且僅當 v -> w 的任意一條邊e都知足 distTo[w] <= distTo[v] + e.weight(),它們是最短路徑

通用最短路徑算法

  1. distTo[s]=0, distTo[v]=INFINITY(v≠s)

  2. 放鬆G中的任意邊,直到不存在有效邊爲止

Dijkstra算法

算法步驟

非負權重

  1. distTo[s]=0, distTo[v]=INFINITY(v≠s)

  2. 將distTo[]中 離頂點s最近的非樹頂點 放鬆, 並加入到樹中

  3. 重複2,直到全部頂點都在樹中 或者 全部的非樹頂點的distTo[]值均爲無窮大

DijkstraSP 代碼

  • 複雜度

    • 空間:V

    • 時間:ElogV

public class DijkstraSP {
    private DirectedEdge[] edgeTo; //記錄路徑
    private double[] distTo; //記錄權重
    private IndexMinPQ<Double> pq; //優先隊列

    public DijkstraSP(EdgeWeightedDigraph G, int s) {
        edgeTo = new DirectedEdge[G.V()];
        distTo = new double[G.V()];
        pq = new IndexMinPQ<Double>(G.V());
        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY; //初始化距離爲正無窮
        distTo[s] = 0.0; //頂點s到頂點s的距離固然爲0
        pq.insert(s, 0.0); //第一次遇到頂點,插入
        while (!pq.isEmpty()) //直到全部頂點都失效(即全部頂點都已加入到最短路徑中)
            relax(G, pq.delMin()); //鬆弛,每次鬆弛,都從隊列中刪除一個點(即加入到最短路徑中)
    }

    private void relax(EdgeWeightedDigraph G, int v) {
        for (DirectedEdge e : G.adj(v)) { //遍歷從v出發的每一條邊
            int w = e.to(); // v -> w
            if (distTo[w] > distTo[v] + e.weight()) { //若是存在比目前s-->w更短的路徑, s-->v,v->w
                distTo[w] = distTo[v] + e.weight();  //更新距離/權重
                edgeTo[w] = e; //更新路徑
                if (pq.contains(w)) // 隊列中有這個點
                    pq.change(w, distTo[w]); //更新,更新隊列中w的權重distTo[w]的值
                else //隊列中沒有這個點
                    pq.insert(w, distTo[w]); //插入,把點w和權重distTo[w]做爲總體插入到隊列中 
            }
        }
    }

    public double distTo(int v) {
        return distTo[v];
    }

    public boolean hasPathTo(int v) {
        return distTo[v] < Double.POSITIVE_INFINITY;
    }

    public Iterable<DirectedEdge> pathTo(int v) {
        if (!hasPathTo(v))
            return null;
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()])
            path.push(e);
        return path;
    }
}
  • DijkstraSP算法 VS Prim 算法

    • DijkstraSP算法 每次添加的都是離起點最近的非樹頂點

    • Prim 算法 每次添加的是離樹頂點最近的非樹頂點

  • 不須要數組marked[],!marked[v] 等價於 distTo[v]無窮大

  • DijkstraSP算法忽略relax()方法中的distTo[v]部分的代碼,便可獲得Prim算法的即時版本

任意頂點對的最短路徑

  • 頂點s,v的最短路徑怎麼求?

    • 用DijkstraSP算法,並在優先隊列中刪除頂點v後中止

  • 任意頂點對的最短路徑怎麼求?

public class DijkstraAllPairsSP {
    private DijkstraSP[] all;

    DijkstraAllPairsSP(EdgeWeightedDigraph G)
    {
        all = new DijkstraSP[G.V()];
        for (int v = 0; v < G.V(); v++)
            all[v] = new DijkstraSP(G, v);
    }

    Iterable<Edge> path(int s, int t) {
        return all[s].pathTo(t);
    }

    double dist(int s, int t) {
        return all[s].distTo(t);
    }
}

無環加權有向圖的最短路徑算法

更快更簡單更好的算法

  • 線性時間

  • 可以處理負權重

  • 可以解決其餘相關問題,eg 距離最長

算法步驟

  1. distTo[s]=0, distTo[v]=INFINITY(v≠s)

  2. 按照 拓撲順序 放鬆全部頂點

AcyclicSP 代碼

複雜度

  • 時間: E+V

  • 空間: V

public class AcyclicSP {
    private DirectedEdge[] edgeTo;
    private double[] distTo;

    public AcyclicSP(EdgeWeightedDigraph G, int s) {
        edgeTo = new DirectedEdge[G.V()];
        distTo = new double[G.V()];
        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;
        Topological top = new Topological(G); //只增長了這一個!!!就把性能提升了!!!
        for (int v : top.order())
            relax(G, v);
    }


    private void relax(EdgeWeightedDigraph G, int v) {
        for (DirectedEdge e : G.adj(v)) { //遍歷從v出發的每一條邊
            int w = e.to(); // v -> w
            if (distTo[w] > distTo[v] + e.weight()) { //若是存在比目前s-->w更短的路徑, s-->v,v->w
                distTo[w] = distTo[v] + e.weight();  //更新距離/權重
                edgeTo[w] = e; //更新路徑
            }
        }
    }
    public double distTo(int v) {
        return distTo[v];
    }
    public boolean hasPathTo(int v) {
        return distTo[v] > Double.NEGATIVE_INFINITY;
    }
    public Iterable<DirectedEdge> pathTo(int v) {
        if (!hasPathTo(v)) return null;
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
}

最長路徑

作一個副本,將無環加權有向圖的權重取反便可。(相關操做就是判斷的不等號符號改反,初始值設爲負無窮)
副本的最短路徑即爲原圖的最長路徑。

AcyclicLP 代碼

public class AcyclicLP {
    private double[] distTo;          // distTo[v] = distance  of longest s->v path
    private DirectedEdge[] edgeTo;    // edgeTo[v] = last edge on longest s->v path
    public AcyclicLP(EdgeWeightedDigraph G, int s) {
        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];
        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.NEGATIVE_INFINITY;
        distTo[s] = 0.0;
        // relax vertices in toplogical order
        Topological topological = new Topological(G);
        if (!topological.hasOrder())
            throw new IllegalArgumentException("Digraph is not acyclic.");
        for (int v : topological.order()) {
            for (DirectedEdge e : G.adj(v))
                relax(e);
        }
    }
    // relax edge e, but update if you find a *longer* path
    private void relax(DirectedEdge e) {
        int v = e.from(), w = e.to();
        if (distTo[w] < distTo[v] + e.weight()) {
            distTo[w] = distTo[v] + e.weight();
            edgeTo[w] = e;
        }       
    }
    public double distTo(int v) {
        return distTo[v];
    }
    public boolean hasPathTo(int v) {
        return distTo[v] > Double.NEGATIVE_INFINITY;
    }
    public Iterable<DirectedEdge> pathTo(int v) {
        if (!hasPathTo(v)) return null;
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
}

平行任務調度

圖片描述

  • 爲每個點添加一個點做爲任務的結束點,並從結束點出髮指向充分條件。

  • 添加一個起點,一個終點。

輸入文本格式(解讀,共10個任務,任務0耗時41秒,需在1,7,9以前完成...)

10
41.0 1 7 9
51.0 2
50.0
36.0
38.0
45.0
21.0 3 8
32.0 3 8
32.0 2
29.0 4 6
public class CPM {
    public static void main(String[] args) {
        int N = StdIn.readInt();
        StdIn.readLine();
        EdgeWeightedDigraph G;
        G = new EdgeWeightedDigraph(2 * N + 2);
        int s = 2 * N, t = 2 * N + 1; //s爲起點,t爲終點
        for (int i = 0; i < N; i++) {
            String[] a = StdIn.readLine().split("\\s+");
            double duration = Double.parseDouble(a[0]);
            G.addEdge(new DirectedEdge(i, i + N, duration)); //添加一個點做爲任務的結束點
            G.addEdge(new DirectedEdge(s, i, 0.0)); //和起點相連
            G.addEdge(new DirectedEdge(i + N, t, 0.0));//任務的結束點和終點相連
            for (int j = 1; j < a.length; j++) {
                int successor = Integer.parseInt(a[j]);//讀取充分條件
                G.addEdge(new DirectedEdge(i + N, successor, 0.0));//任務的結束點和充分條件相連
            }
        }
        AcyclicLP lp = new AcyclicLP(G, s); //最長路徑
        StdOut.println("Start times:");
        for (int i = 0; i < N; i++)
            StdOut.printf("%4d: %5.1f\n", i, lp.distTo(i));
        StdOut.printf("Finish time: %5.1f\n", lp.distTo(t));
    }
}

相對最後期限下的並行任務調度

圖片描述

  • 再加一個限制:deadline,也就是截止時間限制(相對某個任務的截止時間,好比2號任務必須在4號任務啓動的12個單位時間內開始)。

  • 方法是同上面同樣構造圖,同時會添加負權重邊,再將全部邊取反,而後求最短路徑

  • 最短路徑存在則可行(沒有負權重環就是可行的調度)。

通常有向加權圖的最短路徑問題

  • 考慮有環也可能負邊的最短路徑問題

  • 負權重環會致使繞圈現象,所以負權重環存在求不出最短路徑

Bellman-ford算法

  • 以任意順序放鬆全部邊

  • 重複V輪

  • 複雜度

    • 時間: EV

    • 空間: V

public BellmanFord_BruceAlg() {
    for (int pass = 0; pass < G.V(); pass++) //第i輪
        for (v = 0; v < G.V(); v++) //在每一輪中放鬆全部邊
            for (DirectedEdge e : G.adj(v))
                relax(e);
}

基於隊列的Bellman-ford算法

  • 在後幾輪中,不少邊的放鬆都不會成功

  • 只有上一輪distTo[]的值發生改變的頂點指出的邊,才能改變其餘頂點的distTo[]值

  • 用隊列記錄這樣的頂點

public class BellmanFordSP {
    private double[] distTo; // length of path to v
    private DirectedEdge[] edgeTo; // last edge on path to v
    private boolean[] onQ; // Is this vertex on the queue?
    private Queue<Integer> queue; // vertices being relaxed
    private int cost; // number of calls to relax()
    private Iterable<DirectedEdge> cycle; // negative cycle in edgeTo[]?

    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;
        distTo[s] = 0.0;
        queue.enqueue(s);
        onQ[s] = true;
        while (!queue.isEmpty() && !this.hasNegativeCycle()) {
            int v = queue.dequeue();
            onQ[v] = false;
            relax(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]){
                    q.enqueue(w);
                    onQ[w] = true;
                }
            }
            if (cost++ % G.V() == 0) //竟然在這裏判斷是否循環了V輪。。。    
                findNegativeCycle();
        }
    }

    public double distTo(int v) // standard client query methods

    public boolean hasPathTo(int v) // for SPT implementatations

    public Iterable<Edge> pathTo(int v) // (See page 649.)

    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();
    }

    public boolean hasNegativeCycle() {
        return cycle != null;
    }

    public Iterable<Edge> negativeCycle() {
        return cycle;
    }
}
相關文章
相關標籤/搜索