最短路徑之Dijkstra演算法的概念與實現
阿新 • • 發佈:2019-01-23
基本概念
要找出最短路徑,其實就是從起點遍歷所有能到達的頂點,然後計算他們的權重。Dijkstra演算法核心在於邊的鬆弛(relax),可以想象成一根繃緊的橡皮筋,讓它放鬆下來。即是計算源點(s)經過當前點(v)到目標點(w)的權重,如果比目標點(w)之前的權重要小,就替換掉。最終的結果就是生成一顆最小路徑樹。這個演算法和prim演算法非常相似,甚至就是prim即時演算法的變種。如果加權無向圖和加權有向圖的邊和權重對應,最短路徑樹和最小生成樹其實是等價的。
Dijkstra演算法並不能處理權重為負數的邊。
實現
distTo初始為無窮大意味著還沒有訪問過。如果頂點訪問完後也是無窮大,就意味著不能到達。
/**
* 迪傑斯塔拉演算法
* @author yuli
*
*/
public class Dijkstra implements Paths{
private Edge[] edgeTo;//記錄邊的路徑,從起始點到某個點,
private double[] distTo;//到這個頂點的權重
private int s;//開始的頂點
private IndexMinPQ<Double> pq;//用來儲存到最短路徑樹的最小邊,也就是離最短路徑樹最近的邊
public Dijkstra(EdgeWeightedDiGraph graph,int s) {
edgeTo = new Edge[graph.getV()];//初始化頂點路徑
distTo = new double[graph.getV()];//初始化到這個頂點的距離
pq= new IndexMinPQ<>(graph.getV());
this.s = s;
//將起點到這個頂點的距離初始化為無窮大。
for(int i = 0;i<graph.getV();i++){
distTo[i] = Double.POSITIVE_INFINITY;
}
distTo[0 ]= 0.0d;
pq.insert(s, 0.0);
while(!pq.isEmpty()){
relax(graph,pq.delMin());
}
}
/**
* 鬆弛邊,鬆弛就是讓權重變小
*/
private void relax(EdgeWeightedDiGraph graph,int v){
Iterable<Edge> adj = graph.adj(v);
for (Edge edge : adj) {
int w = edge.to();//要鬆弛的目標頂點
//如果目標頂點的權重大於當前頂點的權重+當前邊的權重,就替換
if(distTo[w] > distTo[v] + edge.getWeight()){
distTo[w] = distTo[v] + edge.getWeight();
edgeTo[w] = edge;//到這條邊的最小權重
//通過索引優先佇列來減少比較次數
if(pq.contains(w)){
pq.changeKey(w, distTo[w]);;
}else{
pq.insert(w, distTo[w]);
}
}
}
}
/**
* 取得最短路徑
* @param v
* @return
*/
public Iterable<Integer> pathTo(int v){
if(hasPathTo(v)){
throw new RuntimeException("沒有此路徑");
}
Stack<Integer> stack = new Stack<>();
while(v != s){
Edge e = edgeTo[v];
stack.push(v);
v = e.form();
}
stack.push(s);
return stack;
}
/**
* 獲取最短路徑的權重權重
* @return
*/
public double weight(int v){
return distTo[v];
}
public boolean hasPathTo(int v) {
return distTo[v] == Double.POSITIVE_INFINITY;
}
}