貪心演算法之最短路徑問題(Dijkstra演算法)
阿新 • • 發佈:2019-01-08
1、問題
一個求單源最短路徑的問題。給定有向帶權圖 G =(V, E ),其中每條邊的權是非負實數。此外,給定 V 中的一個頂點,
稱為源點。現在要計算從源到所有其他各頂點的最短路徑長
度,這裡路徑長度指路上各邊的權之和。
2、分析
3、程式碼實現
1、普通C++實現
#include <iostream> #include <cstdio> #include <stack> #include <string> using namespace std; /* 一定要記得如果初始化矩陣的話,肯定需要一個變數儲存長和寬的最大值, 如果看到權重的話,肯定是需要有個變數儲存最大值的權重 */ //城市的節點數目的最大值 const int MAX_CITY_NUM = 100; //節點權值的最大值 const int MAX_POLICY = 1e7; //初始化權重矩陣 int map[MAX_CITY_NUM][MAX_CITY_NUM]; //源點到各個頂點的最短具體陣列 int dist[MAX_CITY_NUM]; //下標表示當前節點值,然後值儲存為上個節點值 int p[MAX_CITY_NUM]; //城市的節點數目和線段的個數 int n, m; //是否加入集合S,如果在集合S裡面的話,值為true,否則在集合S-V裡面,值為false; bool flag[MAX_CITY_NUM]; //Dijkstra演算法 void dijkstra(int start) { //初始化和源點相連的頂點進行初始化 for (int i = 1; i <= n; ++i) { //先把前節點都設定成false flag[i] = false; //先把和源點關聯在一起的進行初始化,如果沒有源點關聯在一起的話 //就設定為權重的最大值,如果我在下面的條件裡面判斷,部分節點的權重 //可能為0,後面可能有問題 dist[i] = map[start][i]; if (dist[i] != MAX_POLICY) { //dist[i] = map[start][i]; p[i] = start; } else { p[i] = -1; } } flag[start] = true; dist[start] = 0; //一開始,我忘記了dist[i]裡面沒有和源點關聯的值應該是MAX_POLICY //然後我也忘記了p[i]裡面如果沒有放值的話應該初始化為-1 //然後我也忘記了dist[start]=0,源點到源點的權重是0 //上面s集合裡面只有源點,我們接下來需要在s集合裡面新增其它的頂點, for (int i = 1; i <= n; ++i) { //t儲存我們每次找到的最小節點權重,然後min_dist用來每次儲存最小的節點權重,然後每次更新這個資料 int min_dist = MAX_POLICY, t = start; //先在V-S集合裡面找到dist[i]裡面權重最小的資料,然後把頂點加入s集合 for (int j = 1; j <= n; j++) { //這裡需要得到最小的dist[j],所以我們這裡不能用!=,必須用< if (dist[j] < min_dist && !flag[j]) { min_dist = dist[j]; t = j; } } //我們發現程式退出的時候,p[j]裡面只有源點的值是-1,其它的值都不是-1 //所以我們需要在迴圈裡面打個標記,如果進去了,說明不能退出,如果沒有進去 //這個臨時變數t和之前的臨時變數的值是一樣,我們就跳出迴圈 if (t == start) return; //找到之後我們需要先設定flag[j]為true flag[t] = true; //加入到s集合之後,如果發現新權重比在dist[i]裡面要小,就需要更新dist[i] for (int j = 1; j <= n; j++) { //C++裡面false的值是0,不是-1,true的值是1,以後一定不能忘記 if (map[t][j] < MAX_POLICY && !flag[j]) { if (dist[j] > (dist[t] + map[t][j])) { //更新新的定點權重 dist[j] = dist[t] + map[t][j]; //找到之後要記得設定之前的頂點 p[j] = t; } } } } } //打印出每個頂點的路徑,這裡值儲存了前一個節點的key //所以我們需要用到棧的特點,先進後出 void showProcess(int start) { int value; stack<int> stack; for (int i = 1; i <= n; ++i) { value = p[i]; std::cout << "源點"<< start << "到"<< i << "的路徑是"; while (value != -1) { stack.push(value); value = p[value]; } while (!stack.empty()) { //pop函式是出來棧,沒有返回值,先取出棧頂值,然後出棧 int node = stack.top(); stack.pop(); std::cout << node << "-"; } std::cout << i << "最短距離為" << dist[i] << std::endl; } } int main() { //定點u到定點v的權重是w, 然後輸入的起始地點是start; int u, v, w, start; std::cout << "請輸入城市的節點個數" << std::endl; std::cin >> n; if (n <= 0) { std::cout << "輸入的城市節點個數因該大於0" << std::endl; return -1; } std::cout << "請輸入城市之間線路的個數" << std::endl; std::cin >> m; if (m <= 0) { std::cout << "輸入的城市之前的線路個數不能小於0" << std::endl; return -1; } //鄰接舉證的初始化,預設都為最大值,注意這裡下標都是從1開始 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { map[i][j] = MAX_POLICY; } } std::cout << "請輸入城市頂點到城市頂點之前的權重" << std::endl; //這裡也可以使用while(--m),因為不涉及到用i for (int i = 0; i < m; ++i) { std::cin >> u >> v >> w; if (u > n || v > n) std::cout << "您輸入的定點有誤" << std::endl; //如果2次輸入一樣頂點,那麼取最小的 map[u][v] = min(map[u][v], w); } std::cout << "請輸入小明的位置" << std::endl; //請輸入起始的頂點 std::cin >> start; if (start < 0 || start > n) { std::cout << "輸入的起始城市定點有誤" << std::endl; return 0; } dijkstra(start); std::cout << "小明所在的位置 " << start << std::endl; for (int i = 1; i <= n; ++i) { std::cout << "小明(" << start << ")要去的位置是" << i; if (dist[i] == MAX_POLICY) std::cout << "無路可到" << std::endl; else std::cout << "最短距離為" << dist[i] << std::endl; } showProcess(start); return 0; }
2、類C++實現
#include <iostream> #include <cstdio> #include <stack> #include <string> using namespace std; //城市的節點數目的最大值 const int MAX_CITY_NUM = 100; //節點權值的最大值 const int MAX_POLICY = 1e7; /* 一定要記得如果初始化矩陣的話,肯定需要一個變數儲存長和寬的最大值, 如果看到權重的話,肯定是需要有個變數儲存最大值的權重 */ class Dijkstra { public: //初始化工作 void init(); //dijkstra演算法 void dijkstra(); //顯示源點到其它頂點的經過的頂點 void showProcess(); //顯示源點到各個頂點的最小權重 void showMinPolicy(); private: //城市的節點數目和線段的個數和起始位置 int n, m, start; //初始化權重矩陣 int map[MAX_CITY_NUM][MAX_CITY_NUM]; //源點到各個頂點的最短具體陣列 int dist[MAX_CITY_NUM]; //下標表示當前節點值,然後值儲存為上個節點值 int p[MAX_CITY_NUM]; //是否加入集合S,如果在集合S裡面的話,值為true,否則在集合S-V裡面,值為false; bool flag[MAX_CITY_NUM]; }; //Dijkstra演算法 void Dijkstra::dijkstra() { //初始化和源點相連的頂點進行初始化 for (int i = 1; i <= n; ++i) { //先把前節點都設定成false flag[i] = false; //先把和源點關聯在一起的進行初始化,如果沒有源點關聯在一起的話 //就設定為權重的最大值,如果我在下面的條件裡面判斷,部分節點的權重 //可能為0,後面可能有問題 dist[i] = map[start][i]; if (dist[i] != MAX_POLICY) { //dist[i] = map[start][i]; p[i] = start; } else { p[i] = -1; } } flag[start] = true; dist[start] = 0; //一開始,我忘記了dist[i]裡面沒有和源點關聯的值應該是MAX_POLICY //然後我也忘記了p[i]裡面如果沒有放值的話應該初始化為-1 //然後我也忘記了dist[start]=0,源點到源點的權重是0 //上面s集合裡面只有源點,我們接下來需要在s集合裡面新增其它的頂點, for (int i = 1; i <= n; ++i) { //t儲存我們每次找到的最小節點權重,然後min_dist用來每次儲存最小的節點權重,然後每次更新這個資料 int min_dist = MAX_POLICY, t = start; //先在V-S集合裡面找到dist[i]裡面權重最小的資料,然後把頂點加入s集合 for (int j = 1; j <= n; j++) { //這裡需要得到最小的dist[j],所以我們這裡不能用!=,必須用< if (dist[j] < min_dist && !flag[j]) { min_dist = dist[j]; t = j; } } //我們發現程式退出的時候,p[j]裡面只有源點的值是-1,其它的值都不是-1 //所以我們需要在迴圈裡面打個標記,如果進去了,說明不能退出,如果沒有進去 //這個臨時變數t和之前的臨時變數的值是一樣,我們就跳出迴圈 if (t == start) return; //找到之後我們需要先設定flag[j]為true flag[t] = true; //加入到s集合之後,如果發現新權重比在dist[i]裡面要小,就需要更新dist[i] for (int j = 1; j <= n; j++) { //C++裡面false的值是0,不是-1,true的值是1,以後一定不能忘記 if (map[t][j] < MAX_POLICY && !flag[j]) { if (dist[j] > (dist[t] + map[t][j])) { //更新新的定點權重 dist[j] = dist[t] + map[t][j]; //找到之後要記得設定之前的頂點 p[j] = t; } } } } } //打印出每個頂點的路徑,這裡值儲存了前一個節點的key //所以我們需要用到棧的特點,先進後出 void Dijkstra::showProcess() { int value; stack<int> stack; for (int i = 1; i <= n; ++i) { value = p[i]; std::cout << "源點"<< start << "到"<< i << "的路徑是"; while (value != -1) { stack.push(value); value = p[value]; } while (!stack.empty()) { //pop函式是出來棧,沒有返回值,先取出棧頂值,然後出棧 int node = stack.top(); stack.pop(); std::cout << node << "-"; } std::cout << i << "最短距離為" << dist[i] << std::endl; } } void Dijkstra::init() { //定點u到定點v的權重是w, 然後輸入的起始地點是start; int u, v, w; std::cout << "請輸入城市的節點個數" << std::endl; std::cin >> n; if (n <= 0) { std::cout << "輸入的城市節點個數因該大於0" << std::endl; return; } std::cout << "請輸入城市之間線路的個數" << std::endl; std::cin >> m; if (m <= 0) { std::cout << "輸入的城市之前的線路個數不能小於0" << std::endl; return; } //鄰接舉證的初始化,預設都為最大值,注意這裡下標都是從1開始 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { map[i][j] = MAX_POLICY; } } std::cout << "請輸入城市頂點到城市頂點之前的權重" << std::endl; //這裡也可以使用while(--m),因為不涉及到用i for (int i = 0; i < m; ++i) { std::cin >> u >> v >> w; if (u > n || v > n) std::cout << "您輸入的定點有誤" << std::endl; //如果2次輸入一樣頂點,那麼取最小的 map[u][v] = min(map[u][v], w); } std::cout << "請輸入小明的位置" << std::endl; //請輸入起始的頂點 std::cin >> start; if (start < 0 || start > n) { std::cout << "輸入的起始城市定點有誤" << std::endl; return; } } void Dijkstra::showMinPolicy() { std::cout << "小明所在的位置 " << start << std::endl; for (int i = 1; i <= n; ++i) { std::cout << "小明(" << start << ")要去的位置是" << i; if (dist[i] == MAX_POLICY) std::cout << "無路可到" << std::endl; else std::cout << "最短距離為" << dist[i] << std::endl; } } int main() { Dijkstra dij; dij.init(); dij.dijkstra(); dij.showMinPolicy(); dij.showProcess(); return 0; }
4、執行結果和時間複雜度和空間複雜度
請輸入城市的節點個數 5 請輸入城市之間線路的個數 11 請輸入城市頂點到城市頂點之前的權重 1 5 12 5 1 8 1 2 16 2 1 29 5 2 32 2 4 13 4 2 27 1 3 15 3 1 21 3 4 7 4 3 19 請輸入小明的位置 5 小明所在的位置 5 小明(5)要去的位置是1最短距離為8 小明(5)要去的位置是2最短距離為24 小明(5)要去的位置是3最短距離為23 小明(5)要去的位置是4最短距離為30 小明(5)要去的位置是5最短距離為0 源點5到1的路徑是5-1最短距離為8 源點5到2的路徑是5-1-2最短距離為24 點5到3的路徑是5-1-3最短距離為23 源點5到4的路徑是5-1-3-4最短距離為30 源點5到5的路徑是5最短距離為0
時間複雜度O(n2),空間複雜的O(n);