算法思想:貪心算法
實際問題:單源最短路徑
編程語言:Java
問題描述
單源最短路徑算法,又稱迪傑斯特拉算法。其目的是尋找從一個頂點到其余各頂點的最短路徑算法,解決的是有權圖中最短路徑問題。
算法構造
相關解釋
- 觀測域:假設起點為v點,觀測域便為v點的四周,即v的所有鄰接點;
- 點集 V:圖中所有點的集合;
- 點集 S:已經找到最短路徑的終點集合;
- 數組 D:存儲觀測域內能觀測到的最短路徑,算上起點一共 n 個數值。比如 D[k] 對應在觀測域中能觀測到的,到頂點 k 的最短路徑;
- 鄰接矩陣 a:存儲着有權圖中的邊的信息,是一個二維數組。比如 a[1][2] = 5 表示在有權圖中,點 1 和點 2 之間有邊,且邊的權值為 5。如果兩點之間沒邊,則用負數或則無窮大(∞)表示。
算法步驟
- 第一步:初始化點集 S,將起點 v 收入 S 中。初始化數組 D:D[k] = a[v][k];
- 第二步:找尋次短路徑。即查找數組 D,找出觀測域中最短路徑(v, j):D[j] = min(D[k] | k 不屬於 S)。將點 j 加入點集 S 中;
- 第三步:將 j 的鄰接點並入觀測域,即用 j 的鄰接點更新數組 D;
- 第四步:不斷重復第二步和第三步,直到節點全部壓入 S 中為止。
注:貪心算法的思想主要就體現在第二步和第三步之中。
Java 代碼
本代碼求解的是無向有權圖的最短路徑,如果想求有向有權圖的最短路徑,則只需要將無向圖的鄰接矩陣改為有向圖的鄰接矩陣即可。
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]);
}
}
}