數據結構——圖及其C++實現 多源最短路徑 FloydWarshall算法

目錄

一、前言

二、算法思想

三、代碼實現

四、測試

五、源碼


一、前言

前兩篇學習的Dijkstra算法和Bellman-Ford算法都是用來求解圖的單源最短路徑,即從圖中指定的一個源點出發到圖中其他任意頂點的最短路徑。Dijkstra算法不能求解帶有負權重的圖的最短路徑,而Bellman-Ford算法彌補了這個缺點。本篇文章再來見識一下一個求解多源最短路徑的算法——Floyd-Warshall算法

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

Floyd-Warshall算法是一種解決多源最短路徑問題(任意兩點間的最短路徑)的算法。

Floyd算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法(可以求解帶負權的圖)。 該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。

我們前面學的Dijkstra算法和Bellman-Ford算法,它們是用來求單源最短路徑的,但是我們如果以所有的頂點為起點都走一遍Dijkstra/Bellman-Ford算法的話,其實也可以得到任意兩點間的最短距離。 不過呢,Dijkstra算法的話不可以求解帶負權路徑的圖,而Bellman-Ford算法呢效率又有點低。 ?

二、算法思想

Floyd-Warshall算法考慮的是一條最短路徑的中間節點

設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}取得的一條最短路徑

?如何去求最短路徑p呢?我們先來了解下面的原理

?即Floyd算法本質是三維動態規劃,D[i][j][k]表示從點i到點j只經過0到k個點最短路徑,然后建立起轉移方程,然后通過空間優化,優化掉最后一維度,變成一個最短路徑的迭代算法,最后即得到所有點的最短路。

上面原理中的這個公式,在動態規劃中應該叫狀態轉移方程:

Di,j,k表示從i到j的最短路徑,該路徑經過的中間結點是剩余的結點組成的集合中的結點,假設經過k個結點,編號為1…k,然后這里就分為了兩種情況:

  1. 如果路徑經過了結點k,那么ij的距離就等于ik的距離加上kj的距離,然后剩余就經過k-1個點
  2. 如果不經過結點k,那ij的距離就等于i到j經過k-1個點(不包括k)的距離

那i到j的最短路徑就等于這兩種情況中的最小值

考慮這里如何存儲最短路徑和路徑權重 ?

前面的兩個算法中我們的dist數組和pPath數組都是用了一個一維數組就行了。 但是Floyd-Warshall算法就不一樣了,因為前兩個算法算的是單源最短路徑,而Floyd-Warshall算法是多源最短路徑。 因為前面我們都是一個起點,然后求其它頂點到起點的最短路徑;而現在是多源,即每個頂點都可以是起點,所以我們要記錄每個頂點作為起點時到其它頂點的最短路徑距離和路徑。 那我們就需要用二維數組了。

三、代碼實現

初始化部分

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
{size_t n = _vertexs.size();  // 獲取圖中頂點數量vvDist.resize(n);            // 調整距離矩陣大小為n*nvvpPath.resize(n);           // 調整路徑矩陣大小為n*n// 初始化權值和路徑矩陣
for (size_t i = 0; i < n; ++i) {vvDist[i].resize(n, MAX_W);  // 距離初始化為無窮大(MAX_W)vvpPath[i].resize(n, -1);    // 路徑初始化為無效值(-1)
}

?接著 我們要把圖中所有相連的邊的信息直接更新一下,因為上面我們說了那個公式叫做狀態轉移方程,而這里初始化更新的結果就作為起始狀態,后面通過狀態轉移方程不斷更新得到最終結果

// 直接相連的邊更新
for (size_t i = 0; i < n; ++i) {for (size_t j = 0; j < n; ++j) {if (_matrix[i][j] != MAX_W) {  // 存在直接邊vvDist[i][j] = _matrix[i][j];  // 更新直接距離vvpPath[i][j] = i;             // 記錄前驅為起點i}if (i == j) {  // 對角線處理vvDist[i][j] = W();  // 自身距離設為0(W()生成零值)}}
}

?根據狀態轉移方程更新

// 最短路徑更新:i-> {其他頂點} ->j
for (size_t k = 0; k < n; ++k) {        // 中間頂點kfor (size_t i = 0; i < n; ++i) {     // 起點ifor (size_t j = 0; j < n; ++j) { // 終點j// 嘗試通過k中轉更新路徑
//如果:
//1. ik是相連的
//2. kj是相連的
//3. ik+kj的距離(經過k點)小于ij(不經過k點)的距離,就更新為ik+kj的距離,否則不更新,保存原來不經過k點的距離
//其實就是前面我們給出的狀態轉移方程的判斷(取兩者之間小的那一個)?if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]) {// 更新最短距離vvDist[i][j] = vvDist[i][k] + vvDist[k][j];// 更新路徑前驅(關鍵步驟!)//注意:這里j的上一個結點不能之間給k,因為k->j路徑上k和j不一定之間相連,//有可能也更新了中間結點(k->...->j),//而kj路徑上的j的上一個是誰?就存儲在vvpPath[k][j]里面vvpPath[i][j] = vvpPath[k][j];}}}

四、測試

加一個打印權值和路徑的二維數組的代碼,因為上面那個例子也是把每一步對應的兩個二維數組(矩陣)畫了出來,我們可以打印(每個頂點作為中間結點更新之后的都打印一下)出來觀察對比一下:

// 打印權值和路徑矩陣觀察數據for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (vvDist[i][j] == MAX_W){//cout << "*" << " ";printf("%3c", '*');}else{//cout << vvDist[i][j] << " ";printf("%3d", vvDist[i][j]);}}cout << endl;}cout << endl;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){//cout << vvParentPath[i][j] << " ";printf("%3d", vvpPath[i][j]);}cout << endl;}cout << "=================================" << endl;
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);vector<vector<int>> vvDist;vector<vector<int>> vvParentPath;g.FloydWarshall(vvDist, vvParentPath);}

還是使用上面的圖解中的圖

運行結果

為了方便對比

?∞等價于*? ? NIL等價于-1

另外pPath數組例子中給的直接就是結點本身(他這里的頂點就是1 2 3 4 5),我們用的是頂點映射的下標,所以大家看到比他的值小1

接著我們打印一下任意兩個頂點之間的最短路徑

// 打印任意兩點之間的最短路徑for (size_t i = 0; i < strlen(str); ++i){g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);cout << endl;}

五、源碼

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath){size_t n = _vertexs.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){vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = W();}}}// abcdef  a {} f ||  b {} c// 最短路徑的更新i-> {其他頂點} ->jfor (size_t k = 0; k < n; ++k){for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){// k 作為的中間點嘗試去更新i->j的路徑if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]){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];}}}// 打印權值和路徑矩陣觀察數據for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (vvDist[i][j] == MAX_W){//cout << "*" << " ";printf("%3c", '*');}else{//cout << vvDist[i][j] << " ";printf("%3d", vvDist[i][j]);}}cout << endl;}cout << endl;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){//cout << vvParentPath[i][j] << " ";printf("%3d", vvpPath[i][j]);}cout << endl;}cout << "=================================" << endl;}	}

感謝閱讀!

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

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

相關文章

解決微軟應用商店 (Microsoft store) 打不開,無網絡連接的問題!

很多小伙伴都會遇見微軟應用商店 (Microsoft store)打開后出現無網絡的問題&#xff0c;一般出現這種問題基本都是因為你的電腦安裝了某些銀行的網銀工具&#xff0c;因為網銀工具為了安全會關閉Internet 選項中的最新版本的TLS協議&#xff0c;而微軟商店又需要最新的TLS協議才…

Android—服務+通知=>前臺服務

文章目錄1、Android服務1.1、定義1.2、基本用法1.2.1、定義一個服務1.2.2、服務注冊1.2.3、啟動和停止服務1.2.4、活動和服務進行通信1.3、帶綁定的服務示例1.3.1、定義服務類1.3.2、客戶端&#xff08;Activity&#xff09;綁定與交互?1.3.3、AndroidManifest.xml 注冊?1.3.…

從基礎功能到自主決策, Agent 開發進階路怎么走

Agent 開發進階路線大綱基礎功能實現核心模塊構建環境感知&#xff1a;傳感器數據處理&#xff08;視覺、語音、文本等輸入&#xff09;基礎動作控制&#xff1a;API調用、硬件驅動、簡單反饋機制狀態管理&#xff1a;有限狀態機&#xff08;FSM&#xff09;或行為樹&#xff0…

《動手學深度學習》讀書筆記—9.6編碼器-解碼器架構

本文記錄了自己在閱讀《動手學深度學習》時的一些思考&#xff0c;僅用來作為作者本人的學習筆記&#xff0c;不存在商業用途。 正如我們在9.5機器翻譯中所討論的&#xff0c;機器翻譯是序列轉換模型的一個核心問題&#xff0c;其輸入和輸出都是長度可變的序列。為了處理這種類…

DocBench:面向大模型文檔閱讀系統的評估基準與數據集分析

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 一、數據集概述與核心目標 DocBench 是由研究團隊于2024年提出的首個…

Python高級排序技術:非原生可比對象的自定義排序策略詳解

引言&#xff1a;超越原生比較操作的排序挑戰在Python數據處理中&#xff0c;我們經常需要處理不原生支持比較操作的對象。根據2024年《Python開發者生態系統報告》&#xff0c;在大型項目中&#xff0c;開發者平均需處理28%的自定義對象排序需求&#xff0c;這些對象包括&…

低代碼系統的技術深度:超越“可視化操作”的架構與實現挑戰

在很多非開發者眼中&#xff0c;低代碼平臺似乎只是簡化流程、快速搭建頁面的工具。然而&#xff0c;在真實的企業級應用中&#xff0c;低代碼系統必須面對高并發請求、復雜業務規則、多角色權限、跨系統集成與持續演進等一系列工程挑戰。高效交付&#xff08;Rapid Delivery&a…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 詞云圖-微博評論詞云圖實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解詞云圖-微博評論詞云圖實現 視頻在線地址&…

Webpack核心技能:Webpack安裝配置與模塊化

一、webpack 的安裝和使用1. webpack 簡介webpack 是基于模塊化的打包 (構建)工具&#xff0c;它把一切視為模塊&#xff08;包括 JS、CSS、圖片等資源文件&#xff09;。工作原理&#xff1a;以開發時態的入口模塊為起點遞歸分析所有依賴關系經過壓縮、合并等處理最終生成運行…

數據結構---二級指針(應用場景)、內核鏈表、棧(系統棧、實現方式)、隊列(實現方式、應用)

一、二級指針的應用場景1、在被調函數中&#xff0c;想要修改主調函數中的指針變量&#xff0c;需要傳遞該指針變量的地址&#xff0c;形參用二級指針接收。2、指針數組的數組名是一個二級指針&#xff0c;指針數組的數組名作為參數傳遞時&#xff0c;可用二級指針接收。指針數…

NodeJs學習日志(1):windows安裝使用node.js 安裝express,suquelize,sqlite,nodemon

windows安裝使用node.js 安裝express&#xff0c;suquelize&#xff0c;sqlite 系統是win10&#xff0c;默認已經安裝好nodejs與npm包名作用expressWeb應用框架suquelize數據庫ORMsqlite數據庫nodemon代碼熱重載安裝express 添加express生成器 npm add express-generator4安裝e…

Cervantes:面向滲透測試人員和紅隊的開源協作平臺

Cervantes 是一個專為滲透測試人員和紅隊打造的開源協作平臺。它提供了一個集中式工作區&#xff0c;用于集中管理項目、客戶端、漏洞和報告。通過簡化數據組織和團隊協調&#xff0c;它有助于減少規劃和執行滲透測試所需的時間和復雜性。 作為 OWASP 旗下的開源解決方案&…

[Python 基礎課程]猜數字游戲

使用 Python 實現一個猜數字游戲&#xff0c;先隨機生成一個 1 到 100 之間的一個隨機整數&#xff0c;讓用戶猜測這個數是什么&#xff0c;每次都提示用戶猜大了還是猜小了&#xff0c;如果用戶猜對了&#xff0c;提示用戶猜對了&#xff0c;用了多少次&#xff0c;并且之前每…

文件加密實現

一、不依賴外部庫實現 使用自定義的XOR加密算法結合簡單的密鑰擴展。 實現說明 這個方案不依賴任何外部庫&#xff0c;僅使用C標準庫實現&#xff1a; 加密原理&#xff1a;采用XOR加密算法&#xff0c;這是一種簡單但有效的對稱加密方式&#xff0c;相同的密鑰可以用于加密和解…

Unity輕量觀察相機

一、腳本功能簡介ObserveCamera 是一個可直接掛載到任意 GameObject 上的通用攝像機控制腳本&#xff0c;支持以下功能&#xff1a;鼠標右鍵控制攝像機繞自身旋轉&#xff08;俯仰、水平&#xff09;鼠標左鍵拖拽目標對象進行平移&#xff08;局部 XY 平面移動&#xff09;鼠標…

1深度學習Pytorch-pytorch、tensor的創建、屬性、設備和類型轉換、數據轉換、常見操作(獲取元素、元素運算、形狀改變、相乘、廣播)

文章目錄PyTorchTensor1 Tensor 的創建1.torch.tensor2.torch.Tensor3. 線性張量4. 隨機張量5. 特定數值的張量2 Tensor 常見屬性1 屬性2 設備切換3 類型轉換torch.Tensor.to(dtype)類型專用方法創建張量時直接指定類型與 NumPy 數組的類型互轉4 數據轉換&#xff08;淺拷貝與深…

五、Istio管理網格外部服務

因語雀與csdn markdown 格式有區別&#xff0c;請查看原文&#xff1a; https://www.yuque.com/dycloud/pss8ys 一、Egress Listener 流量策略 前面學習了 sidecar 自動注入原理、inbound Listener、outbound Listener 等概念&#xff0c;也知道了 EgressListener 的流量策略…

Ubuntu20.04 離線安裝 FFmpeg 靜態編譯包

系統版本 Ubuntu20.04 去現場部署項目&#xff0c;發現現場的設備連接的內網&#xff0c;無法使用apt直接安裝ffmpeg &#xff0c;想解決也簡單&#xff0c;數據線連接手機使用共享網絡&#xff0c;再使用命令sudo apt install ffmpeg安裝即可&#xff0c;奈何現場百多臺設備&a…

C語言高級編程技巧與最佳實踐

C語言高級編程技巧與最佳實踐 - 完整版 目錄 宏定義與預處理技巧內存管理高級技巧函數指針與回調機制數據結構設計并發與多線程錯誤處理與異常機制性能優化技巧調試與測試技巧跨平臺編程安全編程實踐綜合演示示例 宏定義與預處理技巧 1. 條件編譯與平臺檢測 /*** 平臺和編譯…

cygwin+php教程(swoole擴展+redis擴展)

cygwin 1.下載cygwin安裝程序 &#xff1a;在Windows上獲得Linux的感覺 ? 2. 打開安裝包&#xff1a;setup-x86_64.exe 3.選擇安裝類型 從互聯網安裝首次安裝下載而不安裝僅下載軟件包不安裝從本地目錄安裝遷移程序時使用 4.選擇安裝目錄 5.選擇本地軟件包目錄&#xff…