圖論算法(三)最短路徑

最短路徑

定義:在一幅加權有向圖中,從頂點s到頂點t的最短路徑是所有從s到t的路徑中的權重最小者。

Dijkstra 算法

Dijkstra算法會生成一顆最短路徑樹,樹的根爲起始頂點s, 樹的分支爲從頂點s到圖G中所有其他頂點的最短路徑。此算法要求圖中的所有權值均爲非負數。與Prim算法類似,Prim算法每次添加的都是離樹最近的非樹頂點,Dijkstra算法每次添加的都是離起點最近的非樹頂點。

首先將distTo[s]初始化爲0,distTo[]中的其他元素初始化爲正無窮,然後將distTo[]最小的非樹頂點放鬆並加入樹中,如此這般,直到所有的頂點都在樹中或者所有的非樹頂點的distTo[]均爲無窮大。

Dijkstra算法示例演示:

從頂點v1到其他各個頂點的最短路徑

首先將數組distTo[]中起始點v1的值設置爲0,然後再查找一個離v1頂點最近的頂點。
我們的頂點集T的初始化爲:T={v1}

既然是求 v1頂點到其餘各個頂點的最短路程,那就先找一個離 v1 頂點最近的頂點。通過數組 DistTo 可知當前離v1頂點最近是 v3頂點。當選擇了 v3 號頂點後,DistTo[2](下標從0開始)的值就已經從“估計值”變爲了“確定值”,即 v1頂點到 v3頂點的最短路程就是當前 DistTo[2]值。將V3加入到T中。
爲什麼呢?因爲目前離 v1頂點最近的是 v3頂點,並且這個圖所有的邊都是正數,那麼肯定不可能通過第三個頂點中轉,使得 v1頂點到 v3頂點的路程進一步縮短了。因爲 v1頂點到其它頂點的路程肯定沒有 v1到 v3頂點短.

OK,既然確定了一個頂點的最短路徑,下面我們就要根據這個新入的頂點V3會有出度,發現以v3 爲弧尾的有: < v3,v4 >,那麼我們看看路徑:v1–v3–v4的長度是否比v1–v4短,其實這個已經是很明顯的了,因爲DistTo[3]代表的就是v1–v4的長度爲無窮大,而v1–v3–v4的長度爲:10+50=60,所以更新DistTo[3]的值,得到如下結果:


因此 DistTo[3]要更新爲 60。這個過程有個專業術語叫做“鬆弛”。即 v1頂點到 v4頂點的路程即 DistTo[3],通過 < v3,v4> 這條邊鬆弛成功。這便是 Dijkstra 算法的主要思想:通過“邊”來鬆弛v1頂點到其餘各個頂點的路程。

然後,我們又從除DistTo[2]和DistTo[0]外的其他值中尋找最小值,發現DistTo[4]的值最小,通過之前是解釋的原理,可以知道v1到v5的最短距離就是DistTo[4]的值,然後,我們把v5加入到集合T中,然後,考慮v5的出度是否會影響我們的數組DistTo的值,v5有兩條出度:< v5,v4>和 < v5,v6>,然後我們發現:v1–v5–v4的長度爲:50,而DistTo[3]的值爲60,所以我們要更新DistTo[3]的值.另外,v1-v5-v6的長度爲:90,而DistTo[5]爲100,所以我們需要更新DistTo[5]的值。更新後的DistTo數組如下圖:

然後,繼續從dis中選擇未確定的頂點的值中選擇一個最小的值,發現dis[3]的值是最小的,所以把v4加入到集合T中,此時集合T={v1,v3,v5,v4},然後,考慮v4的出度是否會影響我們的數組DistTo的值,v4有一條出度:< v4,v6>,然後我們發現:v1–v5–v4–v6的長度爲:60,而dis[5]的值爲90,所以我們要更新DistTo[5]的值,更新後的DistTo數組如下圖:

然後,我們使用同樣原理,分別確定了v6和v2的最短路徑,最後dis的數組的值如下:

因此,從圖中,我們可以發現v1-v2的值爲:∞,代表沒有路徑從v1到達v2。

加權有向圖的數據結構

加權有向邊的數據類型
package chapter4;

public class DirectedEdge {
    private final int v;//邊的起點
    private final int w;//邊的終點
    private final double 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;
    }
}

加權有向圖的數據類型

package chapter4;

import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;

public class EdgeWeightedDigraph {
    private final int V;//頂點總數
    private int E;//邊的總數
    private Bag<DirectedEdge>[] adj;//鄰接表
    
    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){
        //TODO
    }
    public int V(){
        return V;
    }
    public int E(){
        return E;
    }
    public void addEdge(DirectedEdge e){
        adj[e.from()].add(e);
        E++;
    }
    public Iterable<DirectedEdge> adj(int v){
        return adj[v];
    }
    public Iterable<DirectedEdge> edges(){
        Bag<DirectedEdge> bag = new Bag<>();
        for(int v=0;v<V;v++){
            for (DirectedEdge e : adj[v]) {
                bag.add(e);
            }
        }
        return bag;
    }
}

代碼示例

package chapter4;

import edu.princeton.cs.algs4.DirectedEdge;
import edu.princeton.cs.algs4.EdgeWeightedDigraph;
import edu.princeton.cs.algs4.IndexMinPQ;
import edu.princeton.cs.algs4.Stack;

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<>(G.V());

        //初始化每個頂點到起點的距離爲正無窮
        for (int v = 0; v < G.V(); v++) {
            distTo[v]=Double.POSITIVE_INFINITY;
        }
        //將起點到起點的距離設置爲0,並且加入到優先隊列中
        distTo[s]=0;
        pq.insert(s,0.0);
        if(!pq.isEmpty()){
            relax(G,pq.delMin());
        }
    }

    /**
     * 鬆弛頂點
     * @param G
     * @param v
     */
    private void relax(EdgeWeightedDigraph G,int v){
        //頂點v進入鬆弛階段,其edgeTo[v]的值和distTo[v]的值就不會再變動了
        //也就是加入到樹中。
        for (DirectedEdge edge : G.adj(v)) {
            int w=edge.to();
            if(distTo[w]>distTo[v]+edge.weight()){
                //如果w頂點已經被加入到樹中,則不可能進入到這個if塊中
                distTo[w]=distTo[v]+edge.weight();
                edgeTo[w]=edge;
                if(pq.contains(w)){
                    pq.changeKey(w,distTo[w]);
                }else{
                    pq.insert(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> stack = new Stack<>();
        for(DirectedEdge e = edgeTo[v];e!=null;e=edgeTo[e.from()]){
            stack.push(e);
        }
        return stack;
    }
}

其它最短路徑算法學習鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章