迪傑斯特拉算法介紹:
傑斯特拉算法是由荷蘭計算機科學家狄克斯特拉於1959 年提出的,因此又叫狄克斯特拉算法。是從一個頂點到其餘各頂點的最短路徑算法,解決的是有向圖中最短路徑問題。迪傑斯特拉算法主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。
算法思想:
- 對於一個圖G,計算最短路徑時候,需要指定起點v0(從起始點開始計算)
- 把點劃分爲兩組:
1)第一組S:爲已經求出的最短路徑的終點集合(初始時只包含源點v0)
2)第二組V - S: 爲尚未求出的最短路勁的頂點集合(初始時爲V - {v0}) - 算法將按頂點與v0間最短路徑長度遞增的次序,逐個將集合V - S中的頂點加入到集合S中去。在這個過程中,總保持從v0到集合S中各頂點的路徑長度始終不大於到集合V - S中的各頂點的路徑長度。
利用反證法,可以證明此算法:
假設此路徑上有一個頂點不在S中,則說明存在一條終點不在S而長度比此路徑短的路徑。但是這是不可能的。因爲算法是按照路徑長度遞增的次序來產生最短路徑的,故長度比此路徑的所有路徑均已產生,它們的終點必定在S中,即假設不成立。
算法實現思路:
- 圖,這裏我用帶權的鄰接矩陣(matrix)表示(若邊不存在,則權值爲INF)
- 用一個一維數組(isMinDist),記錄從源點v0到終點vi是否已被確定最短路徑長度,true表示確定,false表示尚未確定。
- 一維數組(prefix)記錄從源點v0到終點vi的當前最短路徑上vi的直接前驅頂點序號。其初值爲本身
- 一維數組(mindist):記錄從源點v0到終點vi的當前最短路徑長度。其初值爲:如果從v0到vi有弧,則mindist[i] 爲弧上的權值,否則爲INF。
- 從V - S中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從V - S中移除頂點k。
- 更新V - S中各個頂點到起點v0的距離。之所以更新V - S 中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(v0,vi)的距離可能大於(v0,vk)+(vk,vi)的距離。
- 重複步驟5 和 6
前期代碼準備:
import java.io.IOException;
import java.util.Scanner;
public class Dijkstra {
private char[] vertex; //頂點集合
private int[][] matrix; //鄰接矩陣
private static final int INF = 999999; //最大值
/**
* 創建圖
*/
public Dijkstra() {
Scanner sca = new Scanner(System.in);
int vertexNum = sca.nextInt(); //頂點數
int matrixNum = sca.nextInt(); //邊數
vertex = new char[vertexNum];
vertex = sca.next().toCharArray(); //初始化頂點
//初始化矩陣
matrix = new int [vertexNum][vertexNum];
for(int i = 0; i < vertexNum; i++)
for(int j = 0; j < vertexNum; j++)
matrix[i][j] = (i == j) ? 0 : INF;
for(int i = 0; i < matrixNum; i++) { //初始化邊的權值
char start = readChar(); //邊的起點
char end = readChar(); //邊的終點
int weight = sca.nextInt(); //邊的權值
int startInedx = getLocation(start); //獲取邊的起點座標
int endIndex = getLocation(end); //獲取邊的終點座標
if(startInedx == -1 || endIndex == -1) return;
matrix[startInedx][endIndex] = weight;
matrix[endIndex][startInedx] = weight;
}
sca.close();
}
/**
* 讀取一個輸入字符
* @return
*/
private char readChar() {
char ch = '0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}while(!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
return ch;
}
/**
* 返回字符的位置
*/
private int getLocation(char c) {
for(int i = 0; i < vertex.length; i++)
if(vertex[i] == c) return i;
return -1;
}
public static void main(String[] args) {
Dijkstra dij = new Dijkstra();
dij.dijkstra(0);
}
}
迪傑斯特拉核心算法:
public void dijkstra(int start) {
int num = matrix[0].length;
//前驅節點
int[] prefix = new int[num];
//最短距離組
int[] mindist = new int[num];
//該節點是否已經找到最短路徑
boolean[] isMinDist = new boolean[num];
int snear = 0;
//初始化前驅頂點和第一個頂點到其他頂點的最短路徑
for(int i = 0; i < num; i++) {
prefix[i] = i; //剛開始前驅節點是本身
mindist[i] = matrix[start][i]; //剛開始,第一個頂點到每個頂點的最短距離就是對應權值
isMinDist[i] = false;
}
isMinDist[start] = true; //第一個頂點,已經找到最短路徑,更改狀態
for(int i = 1; i < num; i++) {
//每次循環求得距離start最近的頂點snear和最短距離min
int min = INF;
for(int j = 0; j < num; j++) {
if(!isMinDist[j] && mindist[j] < min) {
min = mindist[j];
snear = j;
}
}
isMinDist[snear] = true; //知道start到此頂點的最短距離
//根據snear修正start到其他所有節點的前驅節點及距離
for(int k = 0; k < num; k++) {
if(!isMinDist[k] && ((min + matrix[snear][k]) < mindist[k])) {
prefix[k] = snear;
mindist[k] = min + matrix[snear][k];
}
}
}
//打印尋找最短路徑
for(int i = 0; i < num; i++)
System.out.println(vertex[start] + "——>" + vertex[i] + ": s = " + mindist[i]);
}
弗洛伊德算法介紹:
Floyd算法又稱爲插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法,與Dijkstra算法類似。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。
算法思想:
給定一個鄰接矩陣,每次加入一個頂點,這個點作爲中轉點,分別求到圖中其他頂點的路徑,如果路徑和原來比,變短,更新dist,path(記錄中轉點)。
算法實現思路:
- 此處我用帶權的鄰接矩陣代表圖
- 二維數組(path)path[i][j] = k ——>頂點 ‘i’ 到頂點 'j’的最短路徑會經過頂點k
- 二維數組(dist) dist[i][j] = sum ——> 頂點 ‘i’ 到頂點 'j’的最短路徑的長度
弗洛伊德核心算法
/**
* Floyd最短路徑
* 統計圖中各個頂點間的最短路徑
* path: path[i][j] = k ——>頂點 'i' 到頂點 'j'的最短路徑會經過頂點k
* dist: dist[i][j] = sum ——> 頂點 'i' 到頂點 'j'的最短路徑的長度
*/
public void floyd(int[][] path, int[][] dist) {
int len = vertex.length;
//初始化
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
dist[i][j] = matrix[i][j]; //頂點 'i' 到 頂點 'j' 的路徑長度爲 'i' 到 'j'的權值
path[i][j] = j; //頂點 'i' 到頂點 'j' 的最短路徑是經過頂點j
}
}
//計算最短路徑
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
for(int k = 0; k < len; k++) {
//如果經過下標爲k頂點路徑比原兩點間路徑更短,則更新dist[j][k] 和 path[j][k]
int temp = (dist[j][i] == INF || dist[i][k] == INF) ? INF : (dist[j][i] + dist[i][k]);
if(dist[j][k] > temp) {
// 'j' 到 'k'最短路徑 對應的值,爲更小的一個(經過k)
dist[j][k] = temp;
// 'j' 到 'k'的最短路徑對應的路徑,經過i,即標記前驅是哪個點
path[j][k] = path[j][i];
}
}
}
}
//打印Floyd最短路徑的結果
System.out.println("Floyd:");
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++)
System.out.print(dist[i][j] + "\t");
System.out.println();
}
}
注:
兩個核心算法代碼,可以直接放到第一個前期準備代碼裏,直接運行,就可以。
迪傑斯特拉測試數據:
輸入:
4 8
ABCD
A B 1
A D 4
B C 9
B D 2
C A 3
C B 5
C D 8
D C 6
輸出:
A——>A: s = 0
A——>B: s = 1
A——>C: s = 9
A——>D: s = 3
弗洛伊德測試數據:
輸入:
4 8
ABCD
A B 1
A D 4
B C 9
B D 2
C A 3
C B 5
C D 8
D C 6
輸出: