最短路徑問題-Dijkstra演算法
前言:
最短路徑演算法用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。
最短路徑問題是圖論研究中的一個經典演算法問題,是尋找圖(由結點和路徑組成的)中兩結點之間的最短路徑。
文章為了通俗易懂,避免使用一些複雜詞彙,可能會喪失部分表述準確度,但是這樣對我這樣菜的新手更佳友好,文章介紹最為經典的Dijkstra演算法。為了更好的描述演算法增加可讀性,將使用C++作為演算法描述,而且不會考慮演算法優化問題。
// 這一段是廢話
迪傑斯特拉(Dijkstra)演算法是典型的用來解決最短路徑的演算法,也是很多教程中的範例,由荷蘭電腦科學家狄克斯特拉於1959年提出,用來求得從起始點到其他所有點最短路徑。該演算法採用了貪心的思想,每次都查詢與該點距離最近的點,也因為這樣,它不能用來解決存在負權邊的圖。解決的問題大多是這樣的:有一個有向圖G,邊e的長度為l,找出第0點到第N點的最短路徑。
問題描述:
如圖片為有向圖,0點為出發點。
輸入正整數N(0<N<=4)作為終點,請求出從0點到N點的最短路徑長度。
分析:
我們讀入資料就涉及“圖”在計算機中的儲存方式,這裡給出兩個最常用方案:
1.鄰接矩陣
2.鄰接表
為了簡化這篇文章,只介紹方便的鄰接矩陣儲存方式。
鄰接矩陣:
鄰接矩陣其實是一個二維陣列。陣列的每一個元素儲存圖中相鄰兩點之間的長度,陣列兩個下標分別表示對應兩點的標號。
就比如題目中給出的圖片,鄰接矩陣是這樣的:
int matrix[5][5] = { { INF, 99, 50, INF, INF }, { INF, INF, 50, 50, 50 }, { INF, INF, INF, 99, INF }, { INF, INF, INF, INF, 75 }, { INF, INF, INF, INF, INF } };
圖中共有5個點,所以陣列大小為5x5。
比如0點到1點的距離為99,在鄰接矩陣matrix中,表現為matrix[0][1] == 99;但是因為圖為有向圖,1點不能到0點,matrix[0][1] == INF。
INF表示兩點之間距離無限遠。而且因為一個點到一個點本身沒有任何意義,所以一個點到自己的距離也設定為INF。
鄰接矩陣基本上可以應付大部分的情況,但是你會發現,鄰接矩陣在記憶體空間方面佔用較多,文章給出的例子只有5個定點,需要5x5的int陣列,如果點特別多,而且為有向圖可以考慮使用鄰接表的方式儲存。
下面介紹Dijkstra演算法的大致步驟:
我們首先建立兩個陣列,起名為visited 和distance。visited記錄節點是否被訪問過,distance儲存起點到該點的最短路徑。
(1) 初始化。除了起點,其他點都沒有被訪問過,把每個點到起點從矩陣讀取的路徑存到distance陣列。
(2) 遍歷所有沒有訪問過的點,找到當前distance中儲存最近的路徑的點k,標記該點為已訪問。
(3) 遍歷所有沒有訪問過的點,檢查是起點到每個點近還是k點到每個點近,如果是後者,更新當前點distance,儲存k距起點的距離加上k到每個點的距離。
(4) 重複2,3步驟,直到所有點訪問完成。
#include <cstdio>
const int INF = 0x3f3f3f;
static int matrix[5][5] =
{
{ INF, 99, 50, INF, INF },
{ INF, INF, 50, 50, 50 },
{ INF, INF, INF, 99, INF },
{ INF, INF, INF, INF, 75 },
{ INF, INF, INF, INF, INF }
};
/// dest 是目的地點
///
int Dijkstra(const int dest) {
// 頂點的數量
const int vertex_num = 5;
// vis陣列是訪問標記陣列,記錄已經訪問的頂點
// dis陣列儲存起點到當前點的最短路徑
// 都加1是為了訪問安全,防止編譯器和執行時抽風
bool vis[vertex_num + 1] = { 0 };
int dis[vertex_num + 1] = { 0 };
// 初始化0點到各點的距離
for (int i = 1; i < vertex_num; ++i)
dis[i] = matrix[0][i];
// 標記0點,表示已經訪問過
vis[0] = true;
for (int i = 1; i < vertex_num; ++i) {
// 查詢最近的點
int min = INF, k = 0;
for (int j = 0; j < vertex_num; ++j) {
if (! vis[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
// 標記查詢到的最近點
vis[k] = true;
// 判斷是直接點0到點j短,還是經過點k到點j更短
for (int j = 1; j < vertex_num; ++j) {
if (! vis[j] && min + matrix[k][j] < dis[j]) {
dis[j] = min + matrix[k][j];
}
}
}
return dis[dest];
}
int main() {
int n;
while (~scanf("%d", &n)) {
printf("%d\n", Dijkstra(n));
}
return 0;
}