本文將解析如何使用 Dijkstra 算法求解最短路徑問題java
以下圖:算法
就像上圖, 每個點能夠理解成一個岔路口, 線段就是路徑, 線段上的值爲長度, 如何找到從 v0到各個岔路口的最小值, 也就是最短路徑問題數組
最短路徑問題 和 深度廣度搜索同樣, 都是創建在圖這個數據結構上的, 所以咱們能夠選用鄰接表 或者是臨接矩陣 來表示上圖 , 封裝類以下:數據結構
public class Graph01 { // 使用鄰接表的方式表示圖 private LinkedList<Edge> [] graph ; // 圖中一共多少個點 private int size; public Graph01(int size) { this.size = size; this.graph = new LinkedList[this.size]; for (int i = 0; i < size; i++) { graph[i]=new LinkedList<>(); } } // 添加點的方法 public void addEdge(int s, int e, int w) { this.graph[s].add(new Edge(s, e, w)); }
它大概長這個樣子:ide
一條邊有三個描述屬性, 兩邊的頂點 + 權重this
// 邊的封裝類 private class Edge { // 開始點的值 private int start; // 結束點的值 private int end; // 權重 private int weight; public Edge(int start, int end, int weight) { this.start = start; this.end = end; this.weight = weight; } }
使用兩個屬性描述頂點, 分別是 dist 和 id , 好比咱們描述 v3 , 那麼它的id = 3 , dist是到起點v0的距離, 故 dist =13編碼
// 圖中各個點的封裝類 private class Vertex implements Comparable<Vertex> { // 用戶指定的起點到當前點的距離 private int dist; // 頂點的id (v1 v2 中的 1 和 2) private int id; public Vertex(int dist, int startId) { this.dist = dist; this.id = startId; } @Override public int compareTo(Vertex o) { if (o.dist > this.dist) return -1; else return 1; } }
仍是看這個圖: 好比咱們尋找的最短路徑就是 v0 到 其它全部的頂點的最短路徑 , 按照廣度優先順序遍歷這個圖code
尋找 v0 的臨接點 , 因而咱們能發現 v1 v2 v4 v6 這四個點都是v0的臨界點, 而後咱們分別給 v1 v2 v4 v6 這四個點作出以下的標記blog
v1.dist = 13 // dist表示的是 當前點到起點的距離 v2.dist = 8 v4.dist = 30 v6.dist = 32
可是咱們發現, 直接相連 , 並不必定是最短的 , 就像下面這樣 , 雖然都能到v4 , 可是很顯然, 若是按照v0 -> v2 -> v3 -> v4
會更近 , 這就意味着咱們須要不斷的更新每個節點到起點的dist值隊列
v0.dist + v4.dist = 30 v0.dist + v2.dist + v3.dist = 19
那麼, 是否存在一個點到起點v0的dist是 百分百肯定而且不會更改呢??? 沒錯,就是 它全部臨界點中, dist最小的那個點
因而, 咱們的編碼流程就是下面這樣
默認全部的點的 dist = Integer.MAXVALUE
若是看這圖, 用筆跟着上面的邏輯畫一下的話, 就能發現, 確實能找到 v0到其餘各個點的最短路徑, 惟一很差理解的部分就是咱們用黑色加粗的地方,
咱們舉例子解釋一下 , 仍是上圖:
好比, 就從開頭說 :
經過上面的步驟4 , 咱們能夠遍歷到v0的直接臨接點是 v4 , 這是第一次訪問, 因而咱們能夠繼續處理, 而後咱們按照 步驟4.1 進行判斷 v0.dist + 30 < v4.dist
條件知足了(默認全部的點的 dist = Integer.MAXVALUE), 因而咱們就更新 v4.dist = v0.dist + 30 < v4.dist =30
通過了幾輪循環後, 假設當前已是v3了, 這是咱們繼續來到步驟4.1中進行判斷, v3.dist + 6 < v4.dist
咱們發現也是知足的, 由於一開始算出了 v4.dist= 30 , 因而進一步更新這個值, 使v4.dist = v3.dist + 6
, 這樣迭代下面 , 咱們就能獲取到起點 到全部點的最短路徑
最短路徑 dijkstra 算法實現以下:
public void dijkstra(int start, int end) { // 標記是否曾訪問過 boolean[] visited = new boolean[this.size]; // 還路徑 int [] resultArray = new int[this.size]; // 存放圖中的全部的頂點 Vertex[] vertices = new Vertex[this.size]; for (int i = 0; i < vertices.length; i++) { vertices[i]=new Vertex(Integer.MAX_VALUE,i); } // 獲取頂點, 並賦初值爲0 Vertex startVertex = vertices[0]; startVertex.dist = 0; visited[start] = true; // 建立隊列,並將頭結點入隊 Queue<Vertex> queue = new PriorityQueue<>(); queue.add(startVertex); while (!queue.isEmpty()) { // 取出當前距離開始點 dist 最近的點 Vertex minVertex = queue.poll(); // 若是已經找到了頂點就退出去 if (minVertex.id==end) break; // 遍歷當前點的全部臨接點 for (int i = 0; i < graph[minVertex.id].size(); i++) { // 依次獲取出他們的邊 Edge edge = graph[minVertex.id].get(i); // 根據邊的信息, 取出它的取出它的臨界點 Vertex nextVertex = vertices[edge.end]; // 若是當前點 + 當前邊的長度 < 當前點到它臨界點nextVertex的長度 就說明找到了這個直連點更新的點路徑, 因而更新原來的直聯點的數據 if (minVertex.dist + edge.weight < nextVertex.dist) { // 更新原來的不許確的路徑值 nextVertex.dist = minVertex.dist + edge.weight; /** * 數值 0 0 0 2 0 0 0 * 下標 0 1 2 3 4 5 6 * * 下標爲3位置的值爲2 , 表示在圖中 vertex3的前面的vertex2 */ resultArray[nextVertex.id]=minVertex.id; if (!visited[nextVertex.id]){ queue.add(nextVertex); visited[nextVertex.id]=true; } } } } System.out.print(start); print( start,end ,resultArray); } /** * 例如: * 數值: 0 0 0 2 3 1 1 * 下標: 0 1 2 3 4 5 6 * 咱們獲得 從 0 - 6 的路徑該怎麼走呢? 按照以下的順序將方法壓入棧, 彈棧時便可獲取到路徑 * * | 0 0 | * | 0 1 | * | 0 6 | 方法棧 * ---------- */ private void print(int start, int end, int[] resultArray) { if (start==end) return; print(start,resultArray[end],resultArray); System.out.print("->"+end); }