代碼隨想錄算法訓練營 Day60 圖論Ⅹ Bellmen_ford 系列算法

圖論

題目

94. 城市間貨物運輸 I
Bellmen_ford 隊列優化算法 SPFA
大家可以發現 Bellman_ford 算法每次松弛 都是對所有邊進行松弛。
但真正有效的松弛,是基于已經計算過的節點在做的松弛。
本圖中,對所有邊進行松弛,真正有效的松弛,只有松弛邊(節點1->節點2) 和邊(節點1->節點3) 因此只要記錄上一次松馳過的邊即可
模擬過程
我們依然使用minDist數組來表達起點到各個節點的最短距離,例如minDist[3] = 5 表示起點到達節點3 的最小距離為5初始化,起點為節點1,起點到起點的最短距離為0,所以minDist[1] 為 0。將節點1 加入隊列 (下次松弛從節點1開始)
在這里插入圖片描述

從隊列里取出節點1,松弛節點1 作為出發點連接的邊(節點1 -> 節點2)和邊(節點1 -> 節點3)
邊:節點1 -> 節點2,權值為1 ,minDist[2] > minDist[1] + 1 ,更新 minDist[2] = minDist[1] + 1 = 0 + 1 = 1 。邊:節點1 -> 節點3,權值為5 ,minDist[3] > minDist[1] + 5,更新 minDist[3] = minDist[1] + 5 = 0 + 5 = 5。將節點2、節點3 加入隊列
在這里插入圖片描述

從隊列里取出節點2,松弛節點2 作為出發點連接的邊(節點2 -> 節點4)和邊(節點2 -> 節點5)
邊:節點2 -> 節點4,權值為1 ,minDist[4] > minDist[2] + (-3) ,更新 minDist[4] = minDist[2] + (-3) = 1 + (-3) = -2 。邊:節點2 -> 節點5,權值為2 ,minDist[5] > minDist[2] + 2 ,更新 minDist[5] = minDist[2] + 2 = 1 + 2 = 3 。將節點4,節點5 加入隊列
在這里插入圖片描述

從隊列里出去節點3,松弛節點3 作為出發點連接的邊。
因為沒有從節點3作為出發點的邊,所以這里就從隊列里取出節點3就好,不用做其他操作,如圖:
在這里插入圖片描述

從隊列中取出節點4,松弛節點4作為出發點連接的邊(節點4 -> 節點6)邊:節點4 -> 節點6,權值為4 ,minDist[6] > minDist[4] + 4,更新 minDist[6] = minDist[4] + 4 = -2 + 4 = 2。將節點6加入隊列
在這里插入圖片描述

從隊列中取出節點5,松弛節點5作為出發點連接的邊(節點5 -> 節點3),邊(節點5 -> 節點6)
邊:節點5 -> 節點3,權值為1 ,minDist[3] > minDist[5] + 1 ,更新 minDist[3] = minDist[5] + 1 = 3 + 1 = 4。邊:節點5 -> 節點6,權值為-2 ,minDist[6] > minDist[5] + (-2) ,更新 minDist[6] = minDist[5] + (-2) = 3 - 2 = 1。如圖,將節點3加入隊列,因為節點6已經在隊列里所以不用重復添加
在這里插入圖片描述

所以我們在加入隊列的過程可以有一個優化,用visited數組記錄已經在隊列里的元素,已經在隊列的元素不用重復加入,從隊列中取出節點6,松弛節點6 作為出發點連接的邊。節點6作為終點,沒有可以出發的邊。同理從隊列中取出節點3,也沒有可以出發的邊,所以直接從隊列中取出。
在這里插入圖片描述

這樣我們就完成了基于隊列優化的bellman_ford的算法模擬過程。大家可以發現基于隊列優化的算法,要比bellman_ford 算法減少很多無用的松弛情況,特別是對于邊數眾多的大圖優化效果明顯。
代碼實現類似于 dijkstra 的優化版本,需要將圖以鄰接表的形式構建

#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <climits>using namespace std;struct Edge {int to;int val;Edge(int t, int w): to(t), val(w) {}
};int main() {int n, m, x, y, val;cin >> n >> m;vector<list<Edge>> grid(n+1);vector<bool> isInQueue(n+1);// 構造鄰接表for (int i = 0; i < m; ++i) {cin >> x >> y >> val;grid[x].push_back({Edge(y, val)});}int start = 1;int end = n;vector<int> minDist(n+1, INT_MAX);minDist[start] = 0;queue<int> que;que.push(start);// 使用隊列while (!que.empty()) {int cur = que.front();// 從隊列里取出的時候,要取消標記,我們只保證已經在隊列里的元素不用重復加入isInQueue[cur] = false;que.pop();for (Edge e : grid[cur]) {int from = cur;int to = e.to;int price = e.val;if (minDist[to] > minDist[from] + price) {minDist[to] = minDist[from] + price;// 不再添加to這個下標元素 相當于已經訪問過if (isInQueue[to] == false) {que.push(to);isInQueue[to] = true;}}}}if(minDist[end] == INT_MAX) cout << "unconnected" << endl;else cout << minDist[end] << endl;return 0;
}

95. 城市間貨物運輸 II
涉及到負權回路的情況(存在環路,權值為負,會導致無限循環值越來越小)
非負權回路,松弛 n-1 次以上不會有變換,但是設計負權回路就會越來越小
在這里插入圖片描述
那么解決本題的核心思路,就是在 kama94.城市間貨物運輸I 的基礎上,再多松弛一次,看minDist數組是否發生變化。 如果再松弛一次結果變換則存在負權回路

// Bellman_ford 算法實現
#include <iostream>
#include <vector>
#include <climits>using namespace std;int main() {int n, m, x, y, val;cin >> n >> m;vector<vector<int>> grid;vector<int> minDist(n+1, INT_MAX);for (int i = 0; i < m; ++i) {cin >> x >> y >> val;grid.push_back({x, y, val});}int start = 1;int end = n;bool isCircle = false;minDist[start] = 0;// 多做一次負權回路for (int i = 1; i <= n; ++i) {for (vector<int>& edge : grid) {int from = edge[0];int to = edge[1];int price = edge[2];if (i < n) {if (minDist[from] != INT_MAX && minDist[to] > minDist[from] + price) {minDist[to] = minDist[from] + price;}}else {if (minDist[from] != INT_MAX && minDist[to] > minDist[from] + price) isCircle = true;}}}if (isCircle) cout << "circle" << endl;else if (minDist[end] == INT_MAX) cout << "unconnected" << endl;else cout << minDist[end] << endl;return 0;
}// Bellman_ford 隊列優化版本 SPFA 實現
#include <iostream>
#include <vector>
#include <queue>
#include <list>
#include <climits>
using namespace std;struct Edge { //鄰接表int to;  // 鏈接的節點int val; // 邊的權重Edge(int t, int w): to(t), val(w) {}  // 構造函數
};int main() {int n, m, p1, p2, val;cin >> n >> m;vector<list<Edge>> grid(n + 1); // 鄰接表// 將所有邊保存起來for(int i = 0; i < m; i++){cin >> p1 >> p2 >> val;// p1 指向 p2,權值為 valgrid[p1].push_back(Edge(p2, val));}int start = 1;  // 起點int end = n;    // 終點vector<int> minDist(n + 1 , INT_MAX);minDist[start] = 0;queue<int> que;que.push(start); // 隊列里放入起點 vector<int> count(n+1, 0); // 記錄節點加入隊列幾次count[start]++;vector<bool> isInQueue(n+1, false);isInQueue[start] = true;bool flag = false;while (!que.empty()) {int node = que.front(); que.pop();isInQueue[node] = false;for (Edge edge : grid[node]) {int from = node;int to = edge.to;int value = edge.val;if (minDist[to] > minDist[from] + value) { // 開始松弛minDist[to] = minDist[from] + value;if (!isInQueue[to]) {que.push(to);isInQueue[to] = true;count[to]++; if (count[to] == n) {// 如果加入隊列次數超過 n-1次 就說明該圖與負權回路flag = true;while (!que.empty()) que.pop();break;}}}}}if (flag) cout << "circle" << endl;else if (minDist[end] == INT_MAX) {cout << "unconnected" << endl;} else {cout << minDist[end] << endl;}}

96. 城市間貨物運輸 III
給出盡可能路過最多城市的最短路徑
本題是最多經過 k 個城市, 那么是 k + 1條邊相連的節點。 這里可能有錄友想不懂為什么是k + 1,來看這個圖
節點1 最多已經經過2個節點 到達節點4,那么中間是有多少條邊呢,是 3 條邊對吧。
所以本題就是求:起點最多經過k + 1 條邊到達終點的最短距離。
對所有邊松弛一次,相當于計算起點到達與起點一條邊相連的節點的最短距離,那么對所有邊松弛 k + 1次,就是求起點到達與起點k + 1條邊相連的節點的最短距離。 理解以上內容,其實本題代碼就很容易了,bellman_ford 標準寫法是松弛 n-1 次,本題就松弛 k + 1次就好。
但是松弛 k + 1次代碼有錯,具體分析如下
起點為節點1,起點到起點的距離為0,所以 minDist[1] 初始化為0 ,如圖
在這里插入圖片描述

其他節點對應的minDist初始化為max,因為我們要求最小距離,那么還沒有計算過的節點默認是一個最大數,這樣才能更新最小距離。當我們開始對所有邊開始第一次松弛:邊:節點1 -> 節點2,權值為-1 ,minDist[2] > minDist[1] + (-1),更新 minDist[2] = minDist[1] + (-1) = 0 - 1 = -1
在這里插入圖片描述

邊:節點2 -> 節點3,權值為1 ,minDist[3] > minDist[2] + 1 ,更新 minDist[3] = minDist[2] + 1 = -1 + 1 = 0 ,如圖
在這里插入圖片描述

邊:節點3 -> 節點1,權值為-1 ,minDist[1] > minDist[3] + (-1),更新 minDist[1] = 0 + (-1) = -1 ,如圖:
在這里插入圖片描述

邊:節點3 -> 節點4,權值為1 ,minDist[4] > minDist[3] + 1,更新 minDist[4] = 0 + 1 = 1 ,如圖:
在這里插入圖片描述

理論上來說,對所有邊松弛一次,相當于計算 起點到達 與起點一條邊相連的節點 的最短距離
而且對所有邊的后面幾次松弛,同樣是更新了所有的節點,說明至多經過k 個節點這個限制根本沒有限制住,每個節點的數值都被更新了。
這是為什么?
在上面畫圖距離中,對所有邊進行第一次松弛,在計算邊(節點2 -> 節點3) 的時候,更新了節點3。
在這里插入圖片描述

理論上來說節點3 應該在對所有邊第二次松弛的時候才更新。 這因為當時是基于已經計算好的 節點2(minDist[2])來做計算了。minDist[2]在計算邊:(節點1 -> 節點2)的時候剛剛被賦值為 -1。
這樣就造成了一個情況,即:計算minDist數組的時候,基于了本次松弛的 minDist數值,而不是上一次松弛時候minDist的數值。所以在每次計算 minDist 時候,要基于 對所有邊上一次松弛的 minDist 數值才行,所以我們要記錄上一次松弛的minDist。
具體代碼添加了 minDist 的 copy

// 樸素 Bellman_ford方法
#include <iostream>
#include <vector>
#include <climits>using namespace std;int main() {int n, m, x, y, val, src, dst, k;cin >> n >> m;vector<vector<int>> grid;vector<int> minDist(n+1, INT_MAX);vector<int> minDistCopy(n+1);for (int i = 0; i < m; ++i) { cin >> x >> y >> val;grid.push_back({x, y, val});}cin >> src >> dst >> k;minDist[src] = 0;// 最多經過k個城市,最多走過k+1條邊for (int i = 1; i <= k + 1; ++i) {// 獲取上一次計算結果minDistCopy = minDist;for (vector<int>& side : grid) {int from = side[0];int to = side[1];int price = side[2];// 使用上一次copy計算if (minDistCopy[from] != INT_MAX && minDist[to] > minDistCopy[from] + price) {minDist[to] = minDistCopy[from] + price;}}}if (minDist[dst] == INT_MAX) cout << "unreachable" << endl;else cout << minDist[dst] << endl;
}// bellman_ford 隊列優化 SPFA
#include <iostream>
#include <vector>
#include <queue>
#include <list>
#include <climits>using namespace std;struct Edge {int to;int val;Edge(int t, int w) : to(t), val(w) {}
};int main() {int n, m, x, y, val, src, dst, k;cin >> n >> m;vector<list<Edge>> grid(n+1);vector<int> minDist(n+1, INT_MAX);vector<int> minDistCopy(n+1);for (int i = 0; i < m; ++i) {cin >> x >> y >> val;grid[x].push_back(Edge(y, val));}cin >> src >> dst >> k;k++; // k+1minDist[src] = 0;queue<int> que;que.push(src);// k控制松弛次數int qSize = 0;while (k-- && !que.empty()) {// 開啟新的隊列vector<bool> isInQueue(n+1, false);minDistCopy = minDist;qSize = que.size();// 后--保證執行完全while (qSize--) {int cur = que.front();que.pop();isInQueue[cur] = false;for (Edge e : grid[cur]) {int from = cur;int to = e.to;int price = e.val;if (minDist[to] > minDistCopy[from] + price) {minDist[to] = minDistCopy[from] + price;if (!isInQueue[to]) {que.push(to);isInQueue[to] = true;}}}}}if (minDist[dst] == INT_MAX) cout << "unreachable" << endl;else cout << minDist[dst] << endl;
}

邊的順序會影響每一次擴展的結果,給出邊的順序為
我上面講解中,給出的示例是這樣的:

4 4
1 2 -1
2 3 1
3 1 -1
3 4 1
1 4 3

我將示例中邊的順序改一下,給成:

4 4
3 1 -1
3 4 1
2 3 1
1 2 -1
1 4 3

相同的圖就會有不同的結果
在這里插入圖片描述

在這里插入圖片描述

推理一遍,初始化
在這里插入圖片描述
邊:節點3 -> 節點1,權值為-1 ,節點3還沒有被計算過,節點1 不更新。
邊:節點3 -> 節點4,權值為1 ,節點3還沒有被計算過,節點4 不更新。
邊:節點2 -> 節點3,權值為 1 ,節點2還沒有被計算過,節點3 不更新。
邊:節點1 -> 節點2,權值為 -1 ,minDist[2] > minDist[1] + (-1),更新 minDist[2] = 0 + (-1) = -1
在這里插入圖片描述

可以發現 同樣的圖,邊的順序不一樣,使用版本一的代碼 每次松弛更新的節點也是不一樣的。
而邊的順序是隨機的,是題目給我們的,所以本題我們才需要 記錄上一次松弛的minDist,來保障 每一次對所有邊松弛的結果。
為什么必須使用 copy?
94.城市間貨物運輸I,是沒有負權回路的,那么多松弛多少次,對結果都沒有影響。求節點1 到 節點n 的最短路徑,松弛n-1 次就夠了,松弛 大于 n-1次,結果也不會變。 那么在對所有邊進行第一次松弛的時候,如果基于本次計算的 minDist 來計算 minDist (相當于多做松弛了),也是對最終結果沒影響。
95.城市間貨物運輸II 是判斷是否有負權回路,一旦有負權回路,對所有邊松弛 n-1 次以后,在做松弛 minDist 數值一定會變,根據這一點來判斷是否有負權回路。所以,95.城市間貨物運輸II 只需要判斷minDist數值變化了就行,而 minDist 的數值對不對,并不是我們關心的。
其關鍵在于本題的兩個因素:
本題可以有負權回路,說明只要多做松弛,結果是會變的。
本題要求最多經過 k 個節點,對松弛次數是有限制的。
可以使用 dijkstra 嗎?不可以因為 dijkstra 貪心策略導致找不到
參考:代碼隨想錄

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

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

相關文章

Juce實現Table自定義

Juce實現Table自定義 一.總體展示概及概述 在項目中Juce中TableList往往無法滿足用戶需求&#xff0c;頭部和背景及背景顏色設置以及在Cell中添加自定義按鈕&#xff0c;所以需要自己實現自定義TabelList&#xff0c;該示例是展示實現自定義TableList&#xff0c;實現自定義標…

C++ set數據插入、set數據查找、set數據刪除、set數據統計、set排序規則、代碼練習1、2

set數據插入&#xff0c;代碼見下 #include<iostream> #include<set> #include<vector>using namespace std;void printSet(const set<int>& s) {for (set<int>::const_iterator it s.begin(); it ! s.end(); it) {cout << *it <…

深度學習賦能圖像識別:技術、應用與展望

論文&#xff1a; 一、引言? 1.1 研究背景與意義? 在當今數字化時代&#xff0c;圖像作為信息的重要載體&#xff0c;廣泛存在于各個領域。圖像識別技術旨在讓計算機理解和識別圖像內容&#xff0c;將圖像中的對象、場景、行為等信息轉化為計算機能夠處理的符號或數據 &am…

深入解析C++引用:從別名機制到函數特性實踐

1.C引用 1.1引用的概念和定義 引用不是新定義?個變量&#xff0c;而是給已存在變量取了?個別名&#xff0c;編譯器不會為引用變量開辟內存空間&#xff0c;它和它引用的變量共用同?塊內存空間。比如四大名著中林沖&#xff0c;他有一個外號叫豹子頭&#xff0c;類比到C里就…

【從0-1的HTML】第1篇:HTML簡介

1 HTML簡介 HTML是用來描述網頁的一種語言,是超文本標記語言的縮寫(Hyper Text Markup Language),不屬于編程語言的范疇&#xff0c;屬于一種標記語言。 標記語言使用一套標記標簽(Markup tag)&#xff0c;又稱為標簽,HTML就是使用標記標簽來描述網頁。 1.2 HTML標簽 1、HTM…

vue+cesium示例:地形開挖(附源碼下載)

基于cesium和vue繪制多邊形實現地形開挖效果&#xff0c;適合學習Cesium與前端框架結合開發3D可視化項目。 demo源碼運行環境以及配置 運行環境&#xff1a;依賴Node安裝環境&#xff0c;demo本地Node版本:推薦v18。 運行工具&#xff1a;vscode或者其他工具。 配置方式&#x…

qwen大模型在進行詞嵌入向量時,針對的詞表中的唯一數字還是其他的?

qwen大模型在進行詞嵌入向量時,針對的詞表中的唯一數字還是其他的? Qwen大模型進行詞嵌入向量時,針對的是詞表中每個 Token 對應的唯一數字(Token ID) ,核心邏輯結合詞表構建、嵌入過程展開 一、Qwen 詞表與 Token ID Qwen 用 BPE 分詞器(基于 tiktoken,以 cl100k 為…

動態規劃-1143.最長公共子序列-力扣(LeetCode)

一、題目解析 對于給定了兩個字符串中&#xff0c;需要找到最長的公共子序列&#xff0c;也就是兩個字符串所共同擁有的子序列。 二、算法原理 1、狀態表示 dp[i][j]&#xff1a;表示s1的[0,i]和s2的[0,j]區間內所有子序列&#xff0c;最長子序列的長度 2、狀態轉移方程 根…

互聯網c++開發崗位偏少,測開怎么樣?

通過這標題&#xff0c;不難看出問這個問題的&#xff0c;就是沒工作過的。如果工作過&#xff0c;那就是不斷往深的鉆研&#xff0c;路越走越窄&#xff0c;找工作一般就是找原來方向的。沒工作過的&#xff0c;那一般就是學生。 學生找什么方向的工作比較好&#xff1f; 學生…

推薦算法八股

跑路了&#xff0c;暑期0offer&#xff0c;華為主管面掛了&#xff0c;真幽默&#xff0c;性格測評就掛了居然給我一路放到主管面&#xff0c;科大迅飛太囂張&#xff0c;直接跟人說后面要面華為&#xff0c;元戎啟行&#xff0c;學了C后python完全忘了怎么寫&#xff0c;挺尷尬…

Spring Boot微服務架構(九):設計哲學是什么?

一、Spring Boot設計哲學是什么&#xff1f; Spring Boot 的設計哲學可以概括為 ??“約定優于配置”?? 和 ??“開箱即用”??&#xff0c;其核心目標是??極大地簡化基于 Spring 框架的生產級應用的初始搭建和開發過程??&#xff0c;讓開發者能夠快速啟動并運行項目…

前端導入Excel表格

前端如何在 Vue 3 中導入 Excel 文件&#xff08;.xls 和 .xlsx&#xff09;&#xff1f; 在日常開發中&#xff0c;我們經常需要處理 Excel 文件&#xff0c;比如導入數據表格、分析數據等。文章將在 Vue 3 中實現導入 .xls 和 .xlsx 格式的文件&#xff0c;并解析其中的數據…

C++和C#界面開發方式的全面對比

文章目錄 C界面開發方式1. **MFC&#xff08;Microsoft Foundation Classes&#xff09;**2. **Qt**3. **WTL&#xff08;Windows Template Library&#xff09;**4. **wxWidgets**5. **DirectUI** C#界面開發方式1. **WPF&#xff08;Windows Presentation Foundation&#xf…

刷leetcode hot100返航必勝版--鏈表6/3

鏈表初始知識 鏈表種類&#xff1a;單鏈表&#xff0c;雙鏈表&#xff0c;循環鏈表 鏈表初始化 struct ListNode{ int val; ListNode* next; ListNode(int x): val&#xff08;x&#xff09;,next(nullptr) {} }; //初始化 ListNode* head new ListNode(5); 刪除節點、添加…

軟考 系統架構設計師系列知識點之雜項集萃(78)

接前一篇文章&#xff1a;軟考 系統架構設計師系列知識點之雜項集萃&#xff08;77&#xff09; 第139題 以下關于軟件測試工具的敘述&#xff0c;錯誤的是&#xff08;&#xff09;。 A. 靜態測試工具可用于對軟件需求、結構設計、詳細設計和代碼進行評審、走查和審查 B. 靜…

【Unity】云渲染

1 前言 最近在搞Unity云渲染的東西&#xff0c;所以研究了下官方提供的云渲染方案Unity Renderstreaming。注&#xff1a;本文使用的Unity渲染管線是URP。 2 文檔 本文也只是介紹基本的使用方法&#xff0c;更詳細內容參閱官方文檔。官方文檔&#xff1a;Unity Renderstreamin…

組相對策略優化(GRPO):原理及源碼解析

文章目錄 PPO vs GRPOPPO的目標函數GRPO的目標函數KL散度約束與估計ORM監督RL的結果PRM監督RL的過程迭代RL算法流程 GRPO損失的不同版本GRPO源碼解析 DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models PPO vs GRPO PPO的目標函數 J P P O…

Linux或者Windows下PHP版本查看方法總結

確定當前服務器或本地環境中 PHP 的版本,可以通過以下幾種方法進行操作: 1. 通過命令行檢查 這是最直接且常用的方法,適用于本地開發環境或有 SSH 訪問權限的服務器。 方法一:php -v 命令 php -v輸出示例:PHP 8.1.12 (cli) (built: Oct 12 2023 12:34:56) (NTS) Copyri…

[Linux] MySQL源碼編譯安裝

目錄 環境包安裝 創建程序用戶 解壓源碼包 配置cmake ?編輯編譯 安裝 配置修改屬性 屬主和屬組替換成mysql用戶管理 系統環境變量配置 初始化數據庫 服務管理 啟動 環境包安裝 yum -y install ncurses ncurses-devel bison cmake gcc gcc-c 重點強調&#xff1a;采…

【C++項目】負載均衡在線OJ系統-1

文章目錄 前言項目結果演示技術棧&#xff1a;結構與總體思路compiler編譯功能-common/util.hpp 拼接編譯臨時文件-common/log.hpp 開放式日志-common/util.hpp 獲取時間戳方法-秒級-common/util.hpp 文件是否存在-compile_server/compiler.hpp 編譯功能編寫&#xff08;重要&a…