作者版權所有,轉載請注明出處,多謝.http://www.cnblogs.com/Henvealf/p/5574455.html
上一篇介紹了有關圖的表示和遍歷實現.數據結構 -- 簡單圖的實現與遍歷 (Java)現在就來看看關於求圖的最短路徑的問題:
注意:本人學習圖的時候看的書是:
<<數據結構與算法 Java語言版>> (美)Adam Drozdek/著 周翔/譯 機械工業出版社出版
由於要仔細講解內容過多並且本人水平有限,推薦大家找出這本書來看,本篇文章主要是對其中Dijkstra 算法,Ford 算法 ,通用型的糾正標記算法這三個偽代碼的實現.開始頁數為P284.
1.Dijkstra 算法
首先來看一下 Dijkstra 算法,它不能夠處理權值為負的圖.本算法的主要步驟:
1.找出距離起始頂點距離最短的頂點,這里設為頂點nowVertice.
2.遍歷所有與頂點nowVertice相鄰的頂點nextVertice.如果發現選擇nowVertice到達nextVertice的路徑后,nextVertice距離起始頂點的距離比當前的距離小.便更新新的距離.如下:
if(currDist[nextVertice] > currDist[nowVertice] + weight) { //weight為從nowVertice到nextVertice說需要的權重 currDist[nextVertice] = currDist[nowVertice] + weight; }
currDist是一個全局數組,currDist[i]意思就是當前起始頂點到頂點i的距離.
3.將nowVertice從圖中刪除.
4.重復步驟1,直到所有的頂點都被刪除完.
補充,在實現的時候,上面說的刪除並不是真的直接從圖中把某一頂點刪除,這里會使用一個集合來存儲所有的頂點,對該集合中的頂點進行刪除動作,集合如下.
List<Integer> toBeChecked = new LinkedList<>();
和上一篇一樣,這里使用一個名為Graph的類來封裝查找最短路徑的相關內容:
/** * 使用鄰接矩陣實現圖<p> * 深度優先遍歷與廣度優先遍歷<p> * 求最短路徑:<p> * 1. Dijkstra 算法 <p> * 2. Ford 算法 <p> * 3. 通用型的糾正標記算法<p> * Created by Henvealf on 16-5-22. */ public class Graph<T> { private int[][] racs; //鄰接矩陣 private T[] verticeInfo; //各個點所攜帶的信息. private int verticeNum; //頂點的數目, private int[] visitedCount; //記錄訪問 private int[] currDist; //最短路徑算法中用來記錄每個頂點距離起始頂點路徑的長度. public Graph(int[][] racs, T[] verticeInfo){ if(racs.length != racs[0].length){ throw new IllegalArgumentException("racs is not a adjacency matrix!"); } if(racs.length != verticeInfo.length ){ throw new IllegalArgumentException ("Argument of 2 verticeInfo's length is error!"); } this.racs = racs; this.verticeInfo = verticeInfo; verticeNum = racs.length; visitedCount = new int[verticeNum]; } //.......... }
這里是使用的鄰接矩陣來表示圖,想要使用其他表示方法,自行稍微修改一下便可.下面是實現方法的代碼:
1 /** 2 * 使用 Dijkstra算法尋找最短路徑 3 * @param first 路徑開始的頂點 4 * @return 返回最后的最短路徑 5 */ 6 public int[] dijkstraAlgorithm(int first){ 7 if(first < 0 || first >= verticeNum ){ 8 throw new IndexOutOfBoundsException ("should between 0 ~ " + (verticeNum -1)); 9 } 10 setNumberAsInfinitie(); 11 currDist[first] = 0; 12 List<Integer> toBeChecked = new LinkedList<>(); 13 for(int i = 0; i < verticeNum; i ++){ 14 toBeChecked.add(i); 15 } 16 while(!toBeChecked.isEmpty()){ 17 int nowVertice = findMinCurrDistVerticeAndRemoveFormList(toBeChecked); 18 for(int i = 0; i < verticeNum; i ++){ 19 int nextVertice = -1; //鄰接節點 20 int weight = Integer.MAX_VALUE; //到達鄰接節點的權重 21 if(racs[nowVertice][i] != Integer.MAX_VALUE){ //得到鄰接頂點 22 if(toBeChecked.contains(i)){ 23 nextVertice = i; 24 weight = racs[nowVertice][i]; 25 } 26 } 27 if(nextVertice == -1) {continue;} 28 if(currDist[nextVertice] > currDist[nowVertice] + weight){ 29 currDist[nextVertice] = currDist[nowVertice] + weight; 30 } 31 } 32 33 } 34 for(int i = 0; i < currDist.length; i++){ 35 System.out.println("現在頂點 " + verticeInfo[i].toString() + " 距離頂點 " + verticeInfo[first].toString() + " 的最短距離為 " + currDist[i]); 36 } 37 return currDist; 38 } 39 /** 40 * 將currDist數組初始化為無窮大 41 */ 42 private void setNumberAsInfinitie(){ 43 currDist = new int[verticeNum]; 44 for (int i = 0; i < verticeNum; i++){ 45 currDist[i] = Integer.MAX_VALUE; 46 } 47 } 48 49 /** 50 * 尋找出當前距離起始頂點路徑最短的頂點,並將其從toBeCheck中刪除 51 * @param list 52 * @return 53 */ 54 private int findMinCurrDistVerticeAndRemoveFormList(List<Integer> list){ 55 int num = list.get(0); 56 int dist = currDist[list.get(0)]; 57 int listIndex = 0; 58 for(int i = 1; i < list.size(); i ++){ 59 int index = list.get(i); 60 if(currDist[index] < dist) { 61 dist = currDist[index]; 62 num = index; 63 listIndex = i; 64 } 65 } 66 list.remove(listIndex); 67 return num; 68 }
2.Ford 算法
上面提到Dijkstra算法不能處理有負權值的情況,所以自然就有替代方法:Ford方法.
Ford算法並不會像Dijkstra算法一樣去刪除頂點,他時按照一定的順序,來對每個邊進行遍歷並更新設置最短距離.
比如有一個異常簡單的圖:
a-->b-->c-->d
Ford算法要求我們指定邊的遍歷順序,讓每條邊都能夠被走過一次.比如這里我選擇的順序為:b-->c, a-->b, c-->d.
算法就會根據指定的該順序,把圖中所有的邊都訪問一次,每訪問完一遍就是一次迭代.在訪問過程中,和Dijkstra算法相似,回進行如下判斷和更新.
if(currDist[now] > currDist[next] + weight){ currDist[next] = currDist[now] + racs[now][next]; }
然后直到在最后一次迭代中,發現所有的邊都不符合上面的判斷,算法就結束.
實現代碼如下:
1 /** 2 * 使用Ford的方法尋找最短路徑 3 * @param first 路徑開始的頂點 4 */ 5 public int[] fordAlgorithm(int first){ 6 if(first < 0 || first >= verticeNum ){ 7 throw new IndexOutOfBoundsException ("should between 0 ~ " + (verticeNum -1)); 8 } 9 setNumberAsInfinitie(); 10 currDist[first] = 0; 11 while(true){ 12 boolean hasLessEdge = false; //是否有使currDist更小的邊 13 for(int s = 0 ; s < verticeNum; s ++){ 14 for (int e = 0; e < verticeNum; e ++){ 15 if(racs[s][e] != Integer.MAX_VALUE){ 16 int weight = getWeightPreventOverflow(s,e); 17 if(currDist[e] > currDist[s] + weight){ 18 hasLessEdge = true; 19 currDist[e] = currDist[s] + racs[s][e]; 20 } 21 } 22 } 23 } 24 if(!hasLessEdge) { break; } 25 } 26 for(int i = 0; i < currDist.length; i++){ 27 System.out.println("現在頂點 " + verticeInfo[i].toString() + " 距離頂點 " + verticeInfo[first].toString() + " 的最短距離為 " + currDist[i]); 28 } 29 30 return currDist; 31 } 32 33 /** 34 * 處理並獲得權重,並且使得到的結果在進行路徑長度的加減操作時不會出現溢出 35 * @param start 36 * @param end 37 * @return 38 */ 39 private int getWeightPreventOverflow(int start, int end){ 40 int weight = 0; 41 //防止加減法溢出 42 if(currDist[start] == Integer.MAX_VALUE && racs[start][end] > 0){ 43 weight = 0; 44 }else if(currDist[start] == Integer.MIN_VALUE && racs[start][end] < 0){ 45 weight = 0; 46 }else{ 47 weight = racs[start][end]; 48 } 49 return weight; 50 }
3.通用型的糾正標記算法
未完待續...
by 自安/henvealf