最短路徑算法:用於計算一個節點到其他所有節點的最短路徑。是圖論研究中的一個經典算法問題。
一、Dijkstra算法:
典型的最短路徑路由算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。Dijkstra算法能得出最短路徑的最優解,但由於遍歷計算的節點很多,所以效率低。可以用堆優化。
按照路徑長度遞增的次序一步步併入來求取,是貪心算法的一個應用,用來解決單源點到其餘頂點的最短路徑問題。使用了廣度優先搜索BFS解決賦權有向圖或者無向圖的單源最短路徑問題,算法最終得到了一個最短路徑樹。
算法思想:
兩個集合P、Q,P中存放已經確定最短路徑的結點,Q中存放未確定的;兩個數組dis[]、t[],dis中用於存放從起點到該結點的最短路徑值,t用於標識該節點是否在集合P中,在爲1,否則爲0;
1)首先將起點加入集合P中,其餘節點放在Q中,起點的dis爲0,其餘節點爲無窮大。
2)遍歷一次獲取起點v到各個節點的距離,更新dis;
3)得到距離最短的結點x,將其加入P中,表示從起點v到x的最短距離已經確定;
4)計算該結點到其餘節點的距離,判斷dis[i]與dis[x]+(x, i)的距離,如果距離更小則更新dis值;這叫做鬆弛。
5)更新dis後,選出最小的dis,將該結點加入P集合,在一次重複以上步驟,知道Q集合中的所有結點的最短路徑值都已經得到,則算法結束。
實現:
1、基於鄰接矩陣
public class Dijkstra {
// 圖的表示
int numOfVexs;//節點數
int[][] edges;//鄰接矩陣,表示節點間距離,不可達距離爲無窮大
public int[] dijkstra(int v){
if(v < 0||v >= numOfVexs)
return null;
boolean[] st = new boolean[numOfVexs];//默認初始爲false,判斷該節點是否已經計算過最短距離
int[] distance = new int[numOfVexs];//存放源點到其他點距離
//設置不可達距離爲無窮大,這裏爲一個極大值
for(int i = 0; i < numOfVexs; i++){
for(int j = i+1; j < numOfVexs; j++){
if(edges[i][j] == 0){//表示兩個點之間不可達
edges[i][j] = Integer.MAX_VALUE;
edges[j][i] = Integer.MAX_VALUE;
}
}
}
//更新起點到各個節點的距離
for(int i = 0; i < numOfVexs; i++){
distance[i] = edges[v][i];
}
//表明該節點已經計算過最短距離
st[v] = true;
//處理從起點到其餘節點的距離
for(int i = 0; i < numOfVexs; i++){
int min = Integer.MAX_VALUE;
int index = -1;
//比較起點到其餘頂點的路徑長度
for(int j = 0; j < numOfVexs; j++){
// 從起點到當前節點的最短路徑還未找到
if(st[j] == false){
if(distance[j] < min){
index = j;
min = distance[j];
}
}
}
// 找到源點到索引index頂點的最短路徑長度
if(index != -1){
st[index] = true;
}
// 更新當前最短路徑及距離
for(int w = 0; w < numOfVexs; w++){
if(st[w] == false){
if(edges[index][w] != Integer.MAX_VALUE&&(min+edges[index][w] < distance[w])){
distance[w] = min+edges[index][w];
}
}
}
}
return distance;
}
}
2、基於鄰接表
public class Dijkstra {
//鄰接表中表對應的鏈表的頂點
private class ENode{
ENode nextadj;//指向下一個相鄰接點
int adjvex;//該邊所指向的頂點的位置
int weight;//連接兩個頂點的邊的權值
}
//鄰接表中表的頂點
private class VNode{
ENode firstadj;//指向第一條依附在該頂點的弧
}
private int numOfVexs;//節點數
private VNode[] vexs;//頂點數組
public int[] dijkstra2(int v) {
if (v < 0 || v >= numOfVexs)
return null;
boolean[] st = new boolean[numOfVexs];// 默認初始爲false
int[] distance = new int[numOfVexs];// 存放源點到其他點的距離
for (int i = 0; i < numOfVexs; i++) {
distance[i] = Integer.MAX_VALUE;
}
ENode current;
current = vexs[v].firstadj;
while (current != null) {
distance[current.adjvex] = current.weight;
current = current.nextadj;
}
distance[v] = 0;
st[v] = true;
// 處理從源點到其餘頂點的最短路徑
for (int i = 0; i < numOfVexs; i++) {
int min = Integer.MAX_VALUE;
int index = -1;
// 比較從源點到其餘頂點的路徑長度
for (int j = 0; j < numOfVexs; j++) {
// 從源點到j頂點的最短路徑還沒有找到
if (st[j] == false) {
// 從源點到j頂點的路徑長度最小
if (distance[j] < min) {
index = j;
min = distance[j];
}
}
}
// 找到源點到索引爲index頂點的最短路徑長度
if (index != -1)
st[index] = true;
// 更新當前最短路徑及距離
for (int w = 0; w < numOfVexs; w++)
if (st[w] == false) {
current = vexs[w].firstadj;
while (current != null) {
if (current.adjvex == index)
if ((min + current.weight) < distance[w]) {
distance[w] = min + current.weight;
break;
}
current = current.nextadj;
}
}
}
return distance;
}
}
二、Floyd算法
又稱爲插點法,是一種用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法。我們可以求出任意兩個點之間最短路徑。時間複雜度爲O(n^3)。Floyd-Warshall算法不能解決帶有“負權迴路”的圖。
算法特點:Floyd是解決任意兩點間的最短路徑的一種算法,可以正確處理有向圖或無向圖或負權的最短路徑問題(但不能存在負權迴路),同時也被用於計算有向圖的傳遞閉包。
算法思路:
通過Floyd計算圖G =(V,E)中各個頂點的最短路徑時,需要引入兩個矩陣,矩陣D中的元素d[i][j]表示頂點i(第i個頂點)到頂點j(第j個頂點)的距離。矩陣P中的元素p[i][j],表示頂點i到頂點j經過了p[i][j]記錄的值所表示的頂點,也就是說i到j需要經過中間點p[i][j]。
假設圖G中頂點個數爲N,則需要對矩陣D和矩陣P進行N次更新。初始時,矩陣D中頂點d[i][j]的距離爲頂點i到頂點j的權值;如果i和j不相鄰,則d[i][j]=∞,矩陣P的值爲頂點p[i][j]的j的值。
接下來開始,對矩陣D進行N次更新。
1)第1次更新時,如果”d[i][j]的距離” > “d[i][0]+d[0][j]”(d[i][0]+d[0][j]表示”i與j之間經過第1個頂點的距離”),則更新d[i][j]爲”d[i][0]+d[0][j]”,更新p[i][j]=p[i][0],也就是將0這個節點存入。
2)同理,第k次更新時,如果”d[i][j]的 距離” > “d[i][k-1]+d[k-1][j]”,則更新d[i][j]爲”d[i][k-1]+d[k-1][j]”,p[i][j]=p[i][k-1]。更新N次之後,操作完成。
算法實現:
public class Floyd {
private int[][] matrix;//初始的鄰接矩陣(未更新前的矩陣)
private char[] nodes;//節點集合
static int INF=Integer.MAX_VALUE;
public Floyd(int[][] matrix,char[] nodes){
this.matrix=matrix;
this.nodes=nodes;
}
public void floydFunction(int[][] distance,int[][] paths){
//初始化distance[][],paths[][]
for (int i=0;i<nodes.length;i++){
for (int j=0;j<nodes.length;j++){
distance[i][j]=matrix[i][j];//"頂點i"到"頂點j"的路徑長度爲"i到j的權值"。
paths[i][j]=j;//"頂點i"到"頂點j"的最短路徑是經過頂點j。
}
}
System.out.printf("Floyd算法執行前 distance: \n");
print(distance);
System.out.printf("Floyd算法執行前 paths: \n");
print(paths);
//循環更新distance[][],paths[][]
for (int k=0;k<nodes.length;k++){
for (int i=0;i<nodes.length;i++){
for (int j=0;j<nodes.length;j++){
int temp=(distance[i][k]==INF||distance[k][j]==INF)?INF:(distance[i][k]+distance[k][j]);
if(distance[i][j]>temp){
//// "i到j最短路徑"對應的值設,爲更小的一個(即經過k)
distance[i][j]=Math.min(temp,distance[i][j]);
// "i到j最短路徑"對應的路徑,經過k
paths[i][j]=k;
}
}
}
}
System.out.printf("Floyd算法執行後 distance: \n");
print(distance);
System.out.printf("Floyd算法執行後 paths: \n");
print(paths);
}
// 打印floyd最短路徑的結果
public void print(int[][] distance){
for (int i = 0; i < distance.length; i++) {
for (int j = 0; j < distance.length; j++)
System.out.printf("%2d ", distance[i][j]);
System.out.printf("\n");
}
}
public static void main(String[] args){
int[][] matrix={{0,5,INF},{INF,0,6},{7,9,0}};
char[] nodes={'a','b','c'};
Floyd floyd=new Floyd(matrix,nodes);
//長度數組。即,dist[i][j]=sum表示,"頂點i"到"頂點j"的最短路徑的長度是sum。
int[][] distance=new int[nodes.length][nodes.length];
//路徑。path[i][j]=k表示,"頂點i"到"頂點j"的最短路徑會經過頂點k。
int[][] paths=new int[nodes.length][nodes.length];
floyd.floydFunction(distance,paths);
}
}