最短路徑
定義:在一幅加權有向圖中,從頂點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;
}
}