【高階數據結構】圖--最短路徑問題

圖--最短路徑問題

  • 一、單源最短路徑--Dijkstra算法
    • 1、簡介
    • 2、解析
    • 3、代碼
    • 4、測試用例
    • 5、打印最小路徑代碼和測試
    • 6、缺陷:不能使用負路徑
  • 二、單源最短路徑--Bellman-Ford算法
    • 1、簡介
    • 2、解析
      • (1)詳情
        • i、負權問題:一個點只跑一趟找最短路徑(問題大大的)
        • ii、解決負權問題:每個點在更新完最短路徑后繼續暴力再繼續遍歷更新最短路徑
      • (2)優化
        • i、優化策略1:使用bool標記位跳出循環,后面循環不用跑
        • ii、優化策略2:使用SPFA算法優化(隊列)(未做出)
    • 3、代碼
    • 4、測試用例及測試結果
    • 5、負權回路問題
      • (1)問題描述和解析
      • (2)出現負權問題的代碼用例及測試結果
  • 三、多源最短路徑--Floyd-Warshall算法
    • (1)簡介
    • (2)詳細解析(用圖)
    • (3)代碼
    • (4)運行用例及測試結果


一、單源最短路徑–Dijkstra算法

1、簡介

單源最短路徑問題:給定一個圖G = ( V , E ) G=(V,E)G=(V,E),求源結點s ∈ V s∈Vs∈V到圖中每個結點v ∈ V v∈Vv∈V的最短路徑。Dijkstra算法就適用于解決帶權重的有向圖上的單源最短路徑問題,同時算法要求圖中所有邊的權重非負。一般在求解最短路徑的時候都是已知一個起點和一個終點,所以使用Dijkstra算法求解過后也就得到了所需起點到終點的最短路徑。
針對一個帶權有向圖G,將所有結點分為兩組S和Q,S是已經確定最短路徑的結點集合,在初始時為空(初始時就可以將源節點s放入,畢竟源節點到自己的代價是0),Q 為其余未確定最短路徑的結點集合,每次從Q 中找出一個起點到該結點代價最小的結點u ,將u 從Q 中移出,并放入S 中,對u 的每一個相鄰結點v 進行松弛操作。松弛即對每一個相鄰結點v ,判斷源節點s到結點u的代價與u 到v 的代價之和是否比原來s 到v 的代價更小,若代價比原來小則要將s 到v 的代價更新為s 到u 與u 到v 的代價之和,否則維持原樣。如此一直循環直至集合Q 為空,即所有節點都已經查找過一遍并確定了最短路徑,至于一些起點到達不了的結點在算法循環后其代價仍為初始設定的值,不發生變化。Dijkstra算法每次都是選擇V-S中最小的路徑節點來進行更新,并加入S中,所以該算法使用的是貪心策略。
Dijkstra算法存在的問題是不支持圖中帶負權路徑,如果帶有負權路徑,則可能會找不到一些路徑的最短路徑。

2、解析

在這里插入圖片描述

3、代碼

		// Dijkstrala算法// dist是用來存放最小值的表的// pPath是用來存放父親下標的void Dijkstra(const V& src, std::vector<W>& dist, std::vector<int>& pPath){size_t srci = GetIndex(src);size_t n = _vertex.size();// 初始化dist表和pPath表dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = 0; // 當前的第一個位置的距離為0pPath[srci] = srci; // 這個可有可無// 來一個S為存放加入到集合中的已經確定了的點std::vector<bool> S(n, false);for (size_t i = 0; i < n; i++) // 遍歷n次--n個頂點{// 選最短路徑頂點且不在S更新其他路徑size_t u = 0;size_t min = MAX_W;// 遍歷輸入值的表,選最小值for (size_t i = 0; i < n; i++){if (S[i] == false && dist[i] < min) // false是沒有進行訪問過{u = i; // 更新一下當前的最短路徑的頂點min = dist[i]; // 更新點的最小值}}S[u] = true; // 將剛好訪問的這個頂點設置為true已經被訪問過的地方// 松弛算法,更新u連接頂點v  srci->u + u->v <  srci->v  更新for (size_t v = 0; v < n; v++){if (S[v] == false && dist[u] + _matrix[u][v] < dist[v] && _matrix[u][v] != MAX_W){dist[v] = dist[u] + _matrix[u][v]; // 更新一下v的點的值pPath[v] = u; // 更新父下標的點的值}}}}

4、測試用例

	void TestGraphDijkstra(){const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 10);g.AddEdge('s', 'y', 5);g.AddEdge('y', 't', 3);g.AddEdge('y', 'x', 9);g.AddEdge('y', 'z', 2);g.AddEdge('z', 's', 7);g.AddEdge('z', 'x', 6);g.AddEdge('t', 'y', 2);g.AddEdge('t', 'x', 1);g.AddEdge('x', 'z', 4);std::vector<int> dist;std::vector<int> parentPath;g.Dijkstra('s', dist, parentPath);g.PrinrtShotPath('s', dist, parentPath);}

5、打印最小路徑代碼和測試

		void PrinrtShotPath(const V& src, const std::vector<W>& dist, const std::vector<int>& parentPath){size_t N = _vertex.size();size_t srci = GetIndex(src);for (size_t i = 0; i < N; ++i){if (i == srci)continue;std::vector<int> path;int parenti = i;while (parenti != srci){path.push_back(parenti); // 先把自己存進去parenti = parentPath[parenti]; // 往父親去跳}path.push_back(srci); // 最后存初始位置reverse(path.begin(), path.end()); // 逆置一下for (auto pos : path){std::cout << _vertex[pos] << "->";}std::cout << dist[i] << std::endl;}}

在這里插入圖片描述

6、缺陷:不能使用負路徑

const char* str = "sytx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 10);g.AddEdge('s', 'y', 5);g.AddEdge('t', 'y', -7);g.AddEdge('y', 'x', 3);std::vector<int> dist;std::vector<int> parentPath;g.Dijkstra('s', dist, parentPath);g.PrinrtShotPath('s', dist, parentPath);

在這里插入圖片描述

二、單源最短路徑–Bellman-Ford算法

1、簡介

Dijkstra算法只能用來解決正權圖的單源最短路徑問題,但有些題目會出現負權圖。這時這個算法就不能幫助我們解決問題了,而bellman—ford算法可以解決負權圖的單源最短路徑問題。它的優點是可以解決有負權邊的單源最短路徑問題,而且可以用來判斷是否有負權回路。它也有明顯的缺點,它的時間復雜度 O(N*E) (N是點數,E是邊數)普遍是要高于Dijkstra算法O(N2)的。像這里如果我們使用鄰接矩陣實現,那么遍歷所有邊的數量的時間復雜度就是O(N^3),這里也可以看出來Bellman-Ford就是一種暴力求解更新。

2、解析

(1)詳情

i、負權問題:一個點只跑一趟找最短路徑(問題大大的)

在這里插入圖片描述

在這里插入圖片描述

ii、解決負權問題:每個點在更新完最短路徑后繼續暴力再繼續遍歷更新最短路徑

原因還是因為負權路徑的問題,當我們更新完一趟的最短路徑后發現其父親節點是個負數,剛好通過這條路,而我們前面更新的最短路徑不是從這條路走的,那么就需要繼續更新前面這個父親結點的最短路徑為負數,使得這個最短路徑更新成更小的值,那么一切就說通了,我們就需要繼續更新唄,那么就繼續更新n次,每個點繼續遍歷n次。

// 進行n輪循環,原因是因為防止因為某一輪循環后值會產生更改導致的結果不正確// 所以每次進行點的詢問松弛,使得后續的路徑更新成更短路徑for (int k = 0; k < n; k++){std::cout << "第" << k << "輪次" << std::endl;}

我們跟著最終結果來看:
在這里插入圖片描述

(2)優化

i、優化策略1:使用bool標記位跳出循環,后面循環不用跑

因為第一個輪次是將所有的路都跑一遍(初始跑一遍),第二個輪次還是從頭到尾的跑一遍,這回輪次是找負數重新更新一下最短路徑,而假如說路徑的負數并不多的時候,兩個輪次就能全部跑完了,并不需要后面冗余的n-3個輪次,所以我們給個標志位,從第i個輪次發現根本不會更改路勁的情況下我們直接退出,不需要執行下一個輪回的操作。
在這里插入圖片描述

在這里插入圖片描述

ii、優化策略2:使用SPFA算法優化(隊列)(未做出)

在這里插入圖片描述

革命尚未成功,同志仍需努力…

3、代碼

		// 貝爾曼福特算法// 時間復雜度--O(N^3) 空間復雜度--O(N)bool BellmanFord(const V& src, std::vector<W>& dist, std::vector<int>& pPath){size_t n = _vertex.size();size_t srci = GetIndex(src);// vector<W> dist,記錄srci-其他頂點最短路徑權值數組dist.resize(n, MAX_W);// vector<int> pPath 記錄srci-其他頂點最短路徑父頂點數組pPath.resize(n, -1);// 先更新srci->srci為最小值dist[srci] = W();// 進行n輪循環,原因是因為防止因為某一輪循環后值會產生更改導致的結果不正確// 所以每次進行點的詢問松弛,使得后續的路徑更新成更短路徑for (int k = 0; k < n; k++){bool update = false; // 這個判斷標志是提高效率的,只需要進行相對應的輪次更新松弛即可// 第一輪都更新,第二輪往后不一定了for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// srci->i i->jif (dist[i] + _matrix[i][j] < dist[j] && _matrix[i][j] != MAX_W){// 更新最小值+更新pPathdist[j] = dist[i] + _matrix[i][j];pPath[j] = i;update = true;}}}if (update == false){break;}}// 到這里判斷一下是否有負權回路,因為加入說是三個形成一個帶有負值回路,那么每一輪都會將值更新// 這就是負權回路問題,遇到這個負權回路問題也解決不了,那么就返回false即可for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// srci->i i->jif (dist[i] + _matrix[i][j] < dist[j] && _matrix[i][j] != MAX_W){return false;}}}return true;}

4、測試用例及測試結果

	void TestGraphBellmanFord(){const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 6);g.AddEdge('s', 'y', 7);g.AddEdge('y', 'z', 9);g.AddEdge('y', 'x', -3);g.AddEdge('z', 's', 2);g.AddEdge('z', 'x', 7);g.AddEdge('t', 'x', 5);g.AddEdge('t', 'y', 8);g.AddEdge('t', 'z', -4);g.AddEdge('x', 't', -2);std::vector<int> dist;std::vector<int> parentPath;if (g.BellmanFord('s', dist, parentPath)){g.PrinrtShotPath('s', dist, parentPath);}else{std::cout << "存在負權回路" << std::endl;}}

在這里插入圖片描述

在這里插入圖片描述

5、負權回路問題

(1)問題描述和解析

我們出現負權回路的問題,大概率是出現一個環,這個環中剛好有一個或多個邊的權值是負數,如下圖所示:在這里插入圖片描述
那么我們想一想是否這種情況能不能避免或者是有什么算法避免?實際上一旦遇到這個問題就完蛋了,沒有任何算法能夠規避,直接說出現回路問題,不管了!

(2)出現負權問題的代碼用例及測試結果

// 微調圖結構,帶有負權回路的測試const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 6);g.AddEdge('s', 'y', 7);g.AddEdge('y', 'x', -3);g.AddEdge('y', 'z', 9);g.AddEdge('y', 'x', -3);g.AddEdge('y', 's', 1); // 新增g.AddEdge('z', 's', 2);g.AddEdge('z', 'x', 7);g.AddEdge('t', 'x', 5);g.AddEdge('t', 'y', -8); // 更改g.AddEdge('t', 'z', -4);g.AddEdge('x', 't', -2);std::vector<int> dist;std::vector<int> parentPath;if (g.BellmanFord('s', dist, parentPath)){g.PrinrtShotPath('s', dist, parentPath);}else{std::cout << "存在負權回路" << std::endl;}

在這里插入圖片描述

三、多源最短路徑–Floyd-Warshall算法

(1)簡介

Floyd-Warshall算法是解決任意兩點間的最短路徑的一種算法。
Floyd算法考慮的是一條最短路徑的中間節點,即簡單路徑p={v1,v2,…,vn}上除v1和vn的任意節點。
設k是p的一個中間節點,那么從i到j的最短路徑p就被分成i到k和k到j的兩段最短路徑p1,p2。p1是從i到k且中間節點屬于{1,2,…,k-1}取得的一條最短路徑。p2是從k到j且中間節點屬于{1,2,…,k-1}取得的一條最短路徑。

(2)詳細解析(用圖)

在這里插入圖片描述

除了時間復雜度太高幾乎沒別的缺點了,它允許負權路徑的存在。

(3)代碼

		// FloydWarshall算法--多源最短路徑的表示// vvDist是二維用來存放最短路徑的表格// vvpPath是二維用來存放父路徑的void FloydWarshall(std::vector<std::vector<W>>& vvDist, std::vector<std::vector<int>>& vvpPath){size_t n = _vertex.size();vvDist.resize(n); // 有多少行vvpPath.resize(n);// 初始化權值和路徑矩陣for (size_t i = 0; i < n; ++i){vvDist[i].resize(n, MAX_W); // 每一行每一列vvpPath[i].resize(n, -1);}// 先將每條邊更新一下for (size_t i = 0; i < n; i++){for (size_t j = 0; j < n; j++){if (_matrix[i][j] != MAX_W) // 當i->j點是有路的時候{vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i; // 剛好是j的前一個路徑就是i}if (i == j) // 對角線{vvDist[i][j] = W(); // 缺省的默認值}}}// 算法核心:1、中間有k的時候:i->{n個數(包含k)}->j   dist[i][k] + dist[k][j] 與 dist[i][j]比較// 2、中間沒有k的時候,那么就是i->j,也就是和上面的情況一樣了,壓根都不需要管了for (size_t k = 0; k < n/*這里k的值是n的原因在于ij也可以都包進來的*/; k++){for (size_t i = 0; i < n; i++){for (size_t j = 0; j < n; j++){// i到k有路徑 k到j有路徑if (vvDist[i][k] + vvDist[k][j] < vvDist[i][j] && vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W){vvDist[i][j] = vvDist[i][k] + vvDist[k][j];// 找跟j相連的上一個鄰接頂點// 如果k->j 直接相連,上一個點就k,vvpPath[k][j]存就是k// 如果k->j 沒有直接相連,k->...->x->j,vvpPath[k][j]存就是xvvpPath[i][j] = vvpPath[k][j]; // 動態規劃--j的前面一個元素k咯}}}}// 打印權值和路徑矩陣觀察數據for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (vvDist[i][j] == MAX_W){printf("%3c", '*');}else{printf("%3d", vvDist[i][j]);}}std::cout << std::endl;}std::cout << std::endl;// 打印父路徑for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){printf("%3d", vvpPath[i][j]); // 這里打印的是下標}std::cout << std::endl;}std::cout << "=================================" << std::endl;}

(4)運行用例及測試結果

	void TestFloydWarShall(){const char* str = "12345";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('1', '2', 3);g.AddEdge('1', '3', 8);g.AddEdge('1', '5', -4);g.AddEdge('2', '4', 1);g.AddEdge('2', '5', 7);g.AddEdge('3', '2', 4);g.AddEdge('4', '1', 2);g.AddEdge('4', '3', -5);g.AddEdge('5', '4', 6);std::vector<std::vector<int>> vvDist;std::vector<std::vector<int>> vvParentPath;g.FloydWarshall(vvDist, vvParentPath);// 打印任意兩點之間的最短路徑for (size_t i = 0; i < strlen(str); ++i){g.PrinrtShotPath(str[i], vvDist[i], vvParentPath[i]);std::cout << std::endl;}}

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10222.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10222.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10222.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

A股行情訂閱工具,支持股票/可轉債level2/level2數據

簡單使用 ./hqCenter -h-initCodesFile string啟動即訂閱的code (default "./data/initCodes.json")-listen stringhttp監聽地址 (default ":31800")-saveHqFile string行情寫入文件,自動加日期后綴。為空則不寫入文件。 (default "./data/hq")-…

PostGIS之pointcloud

瀚高數據庫 目錄 環境 文檔用途 詳細信息 環境 系統平臺&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14 文檔用途 本文詳細介紹pointcloud&#xff0c;包括&#xff1a;安裝配置、兩個核心數據類型、功能函數、使用PDAL讀寫pgpoingcloud數據等。 詳…

學習前端第三十四天(call,apply,函數綁定;箭頭函數;對象屬性配置)

一、call、apply function fn(x, y) { console.log("hello", x, y, this) }; 1.call方法 作用&#xff1a;調用后執行函數&#xff0c;可以給“this”傳參數 fn.call({ a: 1 }, 1, 2,); 2.apply方法 第一個給“this”傳參數&#xff0c;第二個參數需要是數組形式…

ElementUi中el-table組件使用row-class-name修改指定行顏色

可以通過指定 Table 組件的 row-class-name 屬性來為 Table 中的某一行添加 class&#xff0c;表明該行處于某種狀態。 注意&#xff1a;如果在el-table中使用了stripe這個屬性&#xff0c;這個屬性是帶斑馬紋的表格樣式&#xff0c;它和row-class-name共存時是沒有效果。 注…

【微信開發】微信支付前期準備工作(申請及配置)

1、申請并配置公眾號或微信小程序 1.1 賬戶申請 通過微信公眾平臺&#xff0c;根據指引申請微信小程序或公眾號&#xff0c;申請時需要微信認證&#xff0c;申請流程不在贅述 1.2 信息配置 申請通過后&#xff0c;需進入小程序和公眾號內進行信息配置 1.2.1 小程序信息配置…

Mac YOLO V9推理測試(基于ultralytics)

環境&#xff1a; Mac M1 (MacOS Sonoma 14.3.1) Python 3.11PyTorch 2.1.2 一、準備工作 使用YOLO一般都會接觸ultralytics這個框架&#xff0c;今天來試試用該框架進行YOLO V9模型的推理。 YOLOv9目前提供了四種模型下載&#xff1a;yolov9-c.pt、yolov9-e.pt、gelan-c.p…

lint 代碼規范,手動修復,以及vscode的第三方插件eslint自動修復

ESlint代碼規范 不是語法規范&#xff0c;是一種書寫風格&#xff0c;加多少空格&#xff0c;縮進多少&#xff0c;加不加分號&#xff0c;類似于書信的寫作格式 ESLint:是一個代碼檢查工具&#xff0c;用來檢查你的代碼是否符合指定的規則(你和你的團隊可以自行約定一套規則)…

【管理篇】如何橫向溝通?

目錄標題 什么是橫向溝通&#xff1f;常見溝通問題 如何處理橫向溝通中的問題&#xff1f; 什么是橫向溝通&#xff1f; 所謂橫向溝通&#xff0c;就是和沒有直接匯報關系的合作方之間的溝通&#xff0c;指的是與平級間進行的與完成工作有關的交流&#xff1b;橫向溝通核心的挑…

mqtt定時腳本

需求描述 給mqtt的topic發送信息 對應的topic接收請求后&#xff0c;執行發送短信指令 信息內容 SMS,10086,102 lua的mqtt里面&#xff0c;截取判斷即可 –>可以實現 定時任務&#xff0c; 每月開機一次 發短信&#xff1f; 或者使用開機通知&#xff1f; 定時消費…

Npm Install Docusaurus Demo【npm 安裝 docusaurus 實踐 】

文章目錄 1. 簡介2. 前提2.1 安裝 git2.2 安裝 node 3. 安裝4. 項目結構5. 訪問5.1 localhost 訪問5.2 ip 訪問 1. 簡介 Docusaurus 是一個facebook的開源項目&#xff0c;旨在幫助開發者構建易于維護和部署的文檔網站。它提供了一個簡單的方法來創建專業的文檔網站&#xff0…

共享旅游卡免費旅游真實反饋,有圖有真相?

新伙伴體驗&#xff0c;云南昆大麗6天5晚品質雙人游&#xff0c;真實反饋&#xff01;珠海伙伴蔡總&#xff0c;加入千益暢行共享旅游卡團隊&#xff0c;自己親自體驗“云南昆大麗6天5晚品質雙人游”真實反饋&#xff0c;分享全程內容截圖&#xff0c;無半點虛假&#xff01; …

2024-05-08 postgres-調試及分析-記錄

摘要: 2024-05-08 postgres-調試及分析-記錄 DDL: 創建庫表及插入數據&#xff1a; create database d1;\c d1;create table t1( a int, b int ); create table t2( a int, b int );insert into t1(a,b) values(3,4); insert into t1(a,b) values(5,6);insert into t2(a,b) va…

MongoDB聚合運算符:$trim

MongoDB聚合運算符&#xff1a;$trim 文章目錄 MongoDB聚合運算符&#xff1a;$trim語法使用空白字符 舉例 $trim用來刪除字符串開頭和結尾的空白字符&#xff08;包括空值&#xff09;或指定字符。 語法 { $trim: { input: <string>, chars: <string> } }input&…

react經驗15:拖拽排序組件dnd-kit的使用經驗

應用場景 列表中的成員可鼠標拖拽改變順序 實施步驟 前置引入 import type { DragEndEvent } from dnd-kit/core import { DndContext } from dnd-kit/core import {arrayMove,/*垂直列表使用verticalListSortingStrategy,橫向列表使用horizontalListSortingStrategy*/vert…

springboot引入security,測試接口報Unauthorized

1、報錯截圖 2、當前項目pom文件引入security <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId><version>2.2.2.RELEASE</version> </dependency> 3、解決…

數據結構之圖——探索圖論的奧秘

前言 在這篇文章中&#xff0c;我們一起來看看我們生活中都會用到&#xff0c;但卻不那么熟悉的數據結構——圖&#xff08;英語&#xff1a;graph&#xff09;。我們看下百科定義&#xff1a; 在計算機科學中&#xff0c;圖&#xff08;英語&#xff1a;graph&#xff09;是一…

計算機畢業設計 | vue+springboot汽車銷售管理系統(附源碼)

1&#xff0c;項目介紹 本項目基于spring boot以及Vue開發&#xff0c;前端實現基于PanJiaChen所提供的開源后臺項目vue-element-admin改造。 針對汽車銷售提供客戶信息、車輛信息、訂單信息、銷售人員管理、財務報表等功能&#xff0c;提供經理和銷售兩種角色進行管理。 2&…

某MBTI性格測試系統后臺Getshell

在淘寶購買了性格測試系統源代碼進行環境部署,后進行滲透測試 淘寶源碼鏈接:https://item.taobao.com/item.htm?ftt&id790798788255 (自己學習(代碼審計、算法、環境搭建)知識技能提升) 環境準備 集成環境選的是小皮 phpstudy 創建網站,將源代碼放入網站根目錄配置好數據…

Doris【部署 01】Linux部署MPP數據庫Doris穩定版(下載+安裝+連接+測試)

本次安裝測試的為穩定版2.0.8官方文檔 https://doris.apache.org/zh-CN/docs/2.0/get-starting/quick-start 這個簡短的指南將告訴你如何下載 Doris 最新穩定版本&#xff0c;在單節點上安裝并運行它&#xff0c;包括創建數據庫、數據表、導入數據及查詢等。 Linux部署穩定版Do…