1、問題描述
給定帶權有向圖G =(V,E),其中每條邊的權是非負實數。另外,還給定V中的一個頂點,稱為源。現在要計算從源到所有其他各頂點的最短路長度。這里路的長度是指路上各邊權之和。這個問題通常稱為單源最短路徑問題。
2、Dijkstra算法
Dijkstra算法是解單源最短路徑問題的貪心算法。
其基本思想是,設置頂點集合S並不斷地作貪心選擇來擴充這個集合。一個頂點屬於集合S當且僅當從源到該頂點的最短路徑長度已知。初始時,S中僅含有源。設u是G的某一個頂點,把從源到u且中間只經過S中頂點的路稱為從源到u的特殊路徑,並用數組dist記錄當前每個頂點所對應的最短特殊路徑長度。Dijkstra算法每次從V-S中取出具有最短特殊路長度的頂點u,將u添加到S中,同時對數組dist作必要的修改。一旦S包含了所有V中頂點,dist就記錄了從源到所有其他頂點之間的最短路徑長度。
偽代碼如下:
Dijkstra算法可描述如下,其中輸入帶權有向圖是G=(V,E),V={1,2,…,n},頂點v是源。c是一個二維數組,c[i][j]表示邊(i,j)的權。當(i,j)不屬於E時,c[i][j]是一個大數。dist[i]表示當前從源到頂點i的最短特殊路徑長度。在Dijkstra算法中做貪心選擇時,實際上是考慮當S添加u之后,可能出現一條到頂點的新的特殊路,如果這條新特殊路是先經過老的S到達頂點u,然后從u經過一條邊直接到達頂點i,則這種路的最短長度是dist[u]+c[u][i]。如果dist[u]+c[u][i]<dist[i],則需要更新dist[i]的值。步驟如下:
(1) 用帶權的鄰接矩陣c來表示帶權有向圖, c[i][j]表示弧<vi,vj>上的權值。設S為已知最短路徑的終點的集合,它的初始狀態為空集。從源點v經過S到圖上其余各點vi的當前最短路徑長度的初值為:dist[i]=c[v][i], vi屬於V.
(2) 選擇vu, 使得dist[u]=Min{dist[i] | vi屬於V-S},vj就是長度最短的最短路徑的終點。令S=S U {u}.
(3) 修改從v到集合V-S上任一頂點vi的當前最短路徑長度:如果 dist[u]+c[u][j]< dist[j] 則修改 dist[j]= dist[u]+c[u][j].
(4) 重復操作(2),(3)共n-1次.
算法具體實現如下:

import java.util.Scanner; public class SSSP { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("請輸入圖的頂點和邊的個數(格式:頂點個數 邊個數):"); int n = input.nextInt(); //頂點的個數 int m = input.nextInt(); //邊的個數 System.out.println(); int[][] a = new int[n + 1][n + 1]; //初始化鄰接矩陣 for(int i = 0; i < a.length; i++) { for(int j = 0; j < a.length; j++) { a[i][j] = -1; //初始化沒有邊 } } System.out.println("請輸入圖的路徑長度(格式:起點 終點 長度):"); //總共m條邊 for(int i = 0; i < m; i++) { //起點,范圍1到n int s = input.nextInt(); //終點,范圍1到n int e = input.nextInt(); //長度 int l = input.nextInt(); if(s >= 1 && s <= n && e >= 1 && e <= n) { //無向有權圖 a[s][e] = l; a[e][s] = l; } } System.out.println(); //距離數組 int[] dist = new int[n+1]; //前驅節點數組 int[] prev = new int[n+1]; int v =1 ;//頂點,從1開始 dijkstra(v, a, dist, prev); } /** * 單源最短路徑算法(迪傑斯特拉算法) * @param v 頂點 * @param a 鄰接矩陣表示圖 * @param dist 從頂點v到每個點的距離 * @param prev 前驅節點數組 */ public static void dijkstra(int v, int[][] a, int[] dist, int[] prev) { int n = dist.length; /** * 頂點從1開始,到n結束,一共n個結點 */ if(v > 0 && v <= n) { //頂點是否放入的標志 boolean[] s = new boolean[n]; //初始化 for(int i = 1; i < n; i++) { //初始化為 v 到 i 的距離 dist[i] = a[v][i]; //初始化頂點未放入 s[i] = false; //v到i無路,i的前驅節點置空 if(dist[i] == -1) { prev[i] = 0; } else { prev[i] = v; } } //v到v的距離是0 dist[v] = 0; //頂點放入 s[v] = true; //共掃描n-2次,v到v自己不用掃 for(int i = 1; i < n - 1; i++) { int temp = Integer.MAX_VALUE; //u為下一個被放入的節點 int u = v; //這個for循環為第二步,觀測域為v的觀測域 //遍歷所有頂點找到下一個距離最短的點 for(int j = 1; j < n; j++) { //j未放入,且v到j有路,且v到當前節點路徑更小 if(!s[j] && dist[j] != -1 && dist[j] < temp) { u = j; //temp始終為最小的路徑長度 temp = dist[j]; } } //將得到的下一節點放入 s[u] = true; //這個for循環為第三步,用u更新觀測域 for(int k = 1; k < n; k++) { if(!s[k] && a[u][k] != -1) { int newdist=dist[u] + a[u][k]; if(newdist < dist[k] || dist[k] == -1) { dist[k] = newdist; prev[k] = u; } } } } } for(int i = 2; i < n; i++) { System.out.println(i + "節點的最短距離是:" + dist[i] + ";前驅點是:" + prev[i]); } } }
例,如圖中的有向圖,應用 Dijkstra算法計算從源頂點1到其它頂點間最短路徑的過程如下表所示:




假設S(i,j)={Vi....Vk..Vs...Vj}是從頂點i到j的最短路徑,則有S(i,j)=S(i,k)+S(k,s)+S(s,j)。而S(k,s)不是從k到s的最短距離,那么必定存在另一條從k到s的最短路徑S'(k,s),那么S'(i,j)=S(i,k)+S'(k,s)+S(s,j)<S(i,j)。則與S(i,j)是從i到j的最短路徑相矛盾。因此該性質得證。