最短路問題從入門到負權最短路

一,BFS層次最短路

/*題目描述
題目描述
給出一個 N 個頂點 M 條邊的無向無權圖,頂點編號為 1~N。
問從頂點 1 開始,到其他每個點的最短路有幾條。
輸入格式
第一行包含 2 個正整數 N,M,為圖的頂點數與邊數。
接下來 M 行,每行 2 個正整數 x,y,表示有一條連接頂點 x 和頂點 y 的邊,請注意可能有自環與重邊。
輸出格式
共 N 行,每行一個非負整數,第 i 行輸出從頂點 1 到頂點 i 有多少條不同的最短路,由于答案有可能會很大,
你只需要輸出 ans mod 100003 后的結果即可。如果無法到達頂點 i 則輸出 0。
https://www.luogu.com.cn/problem/P1144
https://vjudge.net/contest/737432#problem/D
*/#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
const long long mod = 1e5 + 3;int n, m, s, dis[maxn];
long long cnt[maxn];
vector<int> g[maxn];void bfs() {queue<int> que;fill(dis+1, dis+n+1, -1);dis[1] = 0;cnt[1] = 1;que.push(1);while (!que.empty()) {int u = que.front();que.pop();for (auto v : g[u]) {if (dis[v] == -1) {dis[v] = dis[u] + 1;cnt[v] = cnt[u];    // u = 3que.push(v);}else if (dis[v] == dis[u] + 1)  // u == 7cnt[v] = (cnt[v] + cnt[u]) % mod;}}
}int main() {scanf("%d%d", &n, &m);for (int i = 0, u, v; i < m; i++) {scanf("%d%d", &u, &v);g[u].push_back(v);g[v].push_back(u);}bfs();for (int i = 1; i <= n; i++)printf("%lld\n", cnt[i]);return 0;
}

我認為BFS層次最短路最關鍵的特征有兩個,首先是層次(通過隊列先入先出的特征來實現),其次是邊權為1,如果邊權不是1的話那么同樣走一步就會有的步伐大有的步伐小,根本無法判斷那條路最短。

二,dijkstra單源最短路

/*題目描述
題目描述
如題,給出一個有向圖,請輸出從某一點出發到所有點的最短路徑長度。
輸入格式
第一行包含三個整數 n,m,s,分別表示點的個數、有向邊的個數、出發點的編號。
接下來 m 行每行包含三個整數 u,v,w,表示一條 u→v 的,長度為 w 的邊。
輸出格式
輸出一行 n 個整數,第 i 個表示 s 到第 i 個點的最短路徑,若不能到達則輸出 2^31?1。
https://www.luogu.com.cn/problem/P4779
https://vjudge.net/contest/737432#problem/B
*/#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5, inf = INT_MAX;
int n, m, s, dis[maxn];
bool vis[maxn];
struct Edge {int v, w;
};
vector<Edge> edge[maxn];struct Node {int u, dist;bool operator < (const Node& b) const {return dist > b.dist;  // 小根堆(優先隊列默認是大根堆,這里取反)}
};
priority_queue<Node> quenode;void dijkstra(int s) {fill(dis + 1, dis + n + 1, inf);dis[s] = 0;quenode.push({ s, 0 });while (!quenode.empty()) {int u = quenode.top().u;quenode.pop();if (!vis[u]) {  // 已經處理過的節點直接跳過vis[u] = true;for (auto& e : edge[u]) {int v = e.v;int w = e.w;// 松弛操作:如果通過u到v的路徑更短,則更新if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;quenode.push({ v, dis[v] });}}}}
}int main() {scanf("%d%d%d", &n, &m, &s);for (int i = 0, u, v, w; i < m; i++) {scanf("%d%d%d", &u, &v, &w);edge[u].push_back({ v, w });}dijkstra(s);for (int i = 1; i <= n; i++)printf("%d ", dis[i]);return 0;
}

反觀dijstra在這方面就有優異的表現,dijstra算法通過根據邊權進行提前排序,以及優先隊列存儲被更新過的新路徑進行高效且簡單地求出單源最短路,但可以說正是因為dijstra的高效,它無法處理負權邊的問題,這是因為dijstra高效在 vis 數組會把一些節點視作“永久節點”,即不會出現更短的路徑通向該節點因此“永久節點”不會再被更新,但是負權邊的存在使得“永久節點”不再永久,有可能會出現比通向該節點的“最短邊”更短的邊,另外,如果一個環的權值之和是負的那么通過這個環的“代價”將變成“動力”,因此會出現死循環。

三,Floyd算法求多源最短路

/*題目描述
給出一張由 n 個點 m 條邊組成的無向圖。
求出所有點對 (i,j) 之間的最短路徑。
輸入格式
第一行為兩個整數 n,m,分別代表點的個數和邊的條數。
接下來 m 行,每行三個整數 u,v,w,代表 u,v 之間存在一條邊權為 w 的邊。
輸出格式
輸出 n 行每行 n 個整數。
第 i 行的第 j 個整數代表從 i 到 j 的最短路徑。
https://www.luogu.com.cn/problem/B3647
*/#include <bits/stdc++.h>
using namespace std;
int n, m, dis[105][105];
int main() {cin >> n >> m;for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)dis[i][j] = (i == j) ? 0 : 1e9;while (m--) {int u, v, w;cin >> u >> v >> w;dis[u][v] = dis[v][u] = min(dis[u][v], w);}for (int k = 1; k <= n; k++)for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);for (int i = 1; i <= n; i++){for (int j = 1; j <= n; j++)cout << dis[i][j] << " ";cout << endl;}return 0;
}

其實 Floyd 算法更像是動態規劃,邏輯上很好理解,dis[i][j] 代表了從 i 到 j 的最短路,然后分類討論途徑第 k 條路徑的最短路,但是我更想介紹的是Floyd算法的一類衍生問題,傳遞閉包。

附:傳遞閉包

/*題目描述
https://www.luogu.com.cn/problem/B3611
*/
#include <bits/stdc++.h>
using namespace std;
int n, a;
bitset<100> f[100];
// f[i][j] = 1/0 目前能不能從i到j
int main() {scanf("%d", &n);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {scanf("%d", &a);if (a)f[i][j] = 1;}}for (int i = 0; i < n; i++) // 經過編號 [0,i] 的點for (int j = 0; j < n; j++) // 對于頂點j來說if (f[j][i])f[j] |= f[i];for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++)printf("%d ", (int)f[i][j]);putchar('\n');}return 0;
}

簡單來說傳遞閉包問題就是判斷任意兩個節點之間是否可以到達,與Floyd相同的地方在于都是多源的問題,不同點在于數組只會存儲 0 或 1 ,所以我們可以用bitset優化一下,大約省了幾十倍的空間,最核心的段落就是if (f[j][i]) f[j] |= f[i]; 這一判斷,當發現 i 與 j 可到達時,i 所能到達的節點 j 也能到達,反之亦然。

四,bellman-ford算法

/*題目描述
https://www.luogu.com.cn/problem/P3385
*/
#include <bits/stdc++.h>
using namespace std;
// bellman-ford判負環
const int maxn = 2e3 + 5, maxm = 3e3 + 5, inf = (1 << 29);int T, n, m, dis[maxn];
struct Edge {int u, v, w;
} e[maxm];int main() {scanf("%d", &T);while (T--) {scanf("%d%d", &n, &m);dis[1] = 0;fill(dis + 2, dis + n + 1, inf);for (int i = 0; i < m; i++)scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);// bellman-fordint c = 0;for (int i = 1; i <= 2 * n; i++) {for (int j = 0; j < m; j++) {int u = e[j].u;int v = e[j].v;int w = e[j].w;if (dis[u] < inf && dis[v] > dis[u] + w)c = i, dis[v] = dis[u] + w;if (w >= 0 && dis[v] < inf && dis[u] > dis[v] + w)c = i, dis[u] = dis[v] + w;}if (c != i)break;}puts(c == 2 * n ? "YES" : "NO");}return 0;
}

理論上來說,同為單源最短路算法,所有dijkstra算法能用的題目bellman-ford也能用,可以理解為一個更容易實現,一個更全面,以上是一個一般的bellman-ford算法,我一般不用,以下是一個隊列優化后的bellman-ford算法,它還有一個響當當的名字(SPFA)。

附:SPFA

#include <bits/stdc++.h>
using namespace std;
// SPFA判負環
const int maxn = 2e3 + 5, maxm = 3e3 + 5, inf = (1<<29);int T, n, m, dis[maxn], cnt[maxn];
bool inq[maxn];
struct Edge {int v, w;
};
vector<Edge> g[maxn];void init() {for (int i = 1; i <= n; i++)g[i].clear();
}bool spfa() {queue<int> que;dis[1] = cnt[1] = 0;fill(dis + 2, dis + n + 1, inf);que.push(1);fill(inq + 1, inq + n + 1, false);inq[1] = true;while (!que.empty()) {int u = que.front();que.pop();inq[u] = false;for (auto &g : g[u]) {if (dis[g.v] > dis[u] + g.w) {dis[g.v] = dis[u] + g.w;cnt[g.v] = cnt[u] + 1;if (cnt[g.v] >= n)return true;if (!inq[g.v])inq[g.v] = true, que.push(g.v);}}}return false;
}int main() {scanf("%d", &T);while (T--) {scanf("%d%d", &n, &m);init();for (int i = 0, u, v, w; i < m; i++) {scanf("%d%d%d", &u, &v, &w);g[u].push_back({v, w});if (w >= 0)g[v].push_back({u, w});}puts(spfa() ? "YES" : "NO");}return 0;
}

?bellman-ford 算法和SPFA算法的核心都是“松弛”(意思就是變短),簡單點來說就是,只有一個終端節點(隊頭節點)的最短距離被更新(松弛)過之后,它將作為開始節點入隊。因為在一個有 n 個頂點的圖中,最短路徑最多包含 n-1 條邊(否則必然存在環),所以我們只需要開一個數組 cnt 記錄每條路徑經過幾個節點,當 cnt[i]?大于等于 n 時說明存在環,而又由于我們算的是最短路徑,正常情況下來講算最短路是不可能算出環的,如果出現環那就一定是負環,詳見代碼。

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

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

相關文章

AI智能體小白入門指南

AI智能體小白入門指南 ——什么是AI智能體&#xff1f;它們如何工作&#xff1f; 一、AI智能體是什么&#xff1f; AI智能體&#xff08;AI Agent&#xff09;是能感知環境、自主決策并執行動作的人工智能系統。 類比理解&#xff1a;像一個“虛擬機器人”或“數字助手”&#…

《設計模式》策略模式

1.策略模式定義 策略模式&#xff08;Strategy Pattern&#xff09;是一種行為型設計模式&#xff0c;它定義了一組算法&#xff0c;將每個算法封裝起來&#xff0c;并使它們可以相互替換&#xff0c;從而讓算法的變化獨立于使用它的客戶&#xff08;Client&#xff09;。 換…

AWS DMS 深度解析:從遷移任務到復制任務 - 全流程指南與最佳實踐

AWS Database Migration Service (DMS) 是一項強大的云服務,用于在源數據庫和目標數據庫之間安全地遷移數據。其核心優勢在于支持幾乎零停機時間的遷移,這主要歸功于其“變更數據捕獲 (CDC)”功能。理解遷移任務 (Migration Task) 和復制任務 (Replication Task) 的關系與操作…

國企社招 | 中國郵政2025年社會招聘開啟

添加圖片注釋&#xff0c;不超過 140 字&#xff08;可選&#xff09; 添加圖片注釋&#xff0c;不超過 140 字&#xff08;可選&#xff09; 添加圖片注釋&#xff0c;不超過 140 字&#xff08;可選&#xff09; 原文鏈接&#xff1a;“郵”你“政”好 | 廣東郵政2025年社會…

linux添加自啟動

linux添加自啟動 配置步驟&#xff1a; 創建systemd服務文件 sudo nano /etc/systemd/system/tme-vod.service將下面artifact中的內容復制到該文件中。 [Unit] DescriptionTME VOD Service Afternetwork.target[Service] Typesimple Userroot Grouproot WorkingDirectory/data/…

輕量級解決方案:如何高效處理Word轉PDF?

文檔格式轉換時&#xff0c;手動逐個處理總顯得效率低下。它的體積小巧&#xff0c;不到1MB&#xff0c;且無界面設計&#xff0c;運行極簡&#xff1a;將其與Word文件放入同一目錄&#xff0c;雙擊啟動&#xff0c;程序便會自動完成所有文檔的PDF轉換。操作零復雜度&#xff0…

Redis 數據傾斜

Redis 數據傾斜指的是在 Redis 集群模式下&#xff0c;數據&#xff08;以及相應的訪問請求和負載&#xff09;在各個分片&#xff08;Shard&#xff09;之間分布嚴重不均勻的現象。這會導致部分節點成為熱點或超載&#xff0c;而其他節點資源閑置&#xff0c;最終引發性能瓶頸…

Java基礎-TCP通信(多發多收和一發一收)

目錄 案例要求&#xff1a; 實現思路&#xff1a; 代碼&#xff1a; User:客戶端 Client:服務端 總結&#xff1a; 案例要求&#xff1a; 實現TCP通信的多發多收和一發一收,多發多收去掉各自的while循環就是一發一收,本文只模擬一發一收 實現思路&#xff1a; 客戶端(U…

WinForm 對話框的 Show 與 ShowDialog:阻塞與非阻塞的抉擇

目錄 核心概念&#xff1a;阻塞與非阻塞 Show 與 ShowDialog 的詳細對比 代碼示例&#xff1a;兩種方式的實現差異 使用 Show () 顯示非模態對話框 使用 ShowDialog () 顯示模態對話框 適用場景分析 適合使用 Show () 的場景 適合使用 ShowDialog () 的場景 最佳實踐與…

曉知識: 動態代理與靜態代理的區別

動態代理與靜態代理的區別 代理模式是一種常見的設計模式&#xff0c;用于在不修改原始類的情況下擴展其功能。代理分為靜態代理和動態代理兩種&#xff0c;它們在實現方式、適用場景和靈活性上有顯著差異。 靜態代理 靜態代理在編譯時就已經確定代理類和被代理類的關系。代理類…

Linux系統編程Day9 -- gdb (linux)和lldb(macOS)調試工具

往期內容回顧 Git 教程&#xff08;初階&#xff09; 基于Linux系統知識的第一個程序 自動化構建工具-make/Makefile gcc/g編譯及鏈接 Vim工具的使用 Linux常用工具&#xff08;yum與vim&#xff09; 一、 Linux 下的調試工具 GDB 一、為什么要學習 GDB&#xff1f; 調試是開發…

數據結構(17)排序(下)

一、計數排序計數排序又稱為鴿巢原理&#xff0c;是對哈希直接定址法的變形應用。操作步驟如下&#xff1a;①統計相同元素出現的次數 ②根據統計的結果將序列回收到原來的序列中比如&#xff0c;現在有一個數組{6,1,2,9,4,2,4,1,4}。該數組中&#xff0c;元素1出現兩次&#…

深度解析 Spring Boot 循環依賴:原理、源碼與解決方案

在 Spring Boot 開發中,循環依賴是一個常見且容易被忽視的技術點。當兩個或多個 Bean 相互引用時,就會形成循環依賴(如 A 依賴 B,B 依賴 A)。初學者往往會困惑:Spring 為什么能自動處理這種看似矛盾的依賴關系?本文將從原理、源碼實現到解決方案,全方位剖析 Spring Boo…

數據庫的基本操作(約束與DQL查詢)

一、約束約束是在表上強制執行的數據規則&#xff0c;用于確保數據的完整性和一致性&#xff08;1&#xff09;約束類型MySQL中支持多種約束類型&#xff1a;①主鍵約束&#xff08;PRIMARY KEY&#xff09; ②自增約束&#xff08;AUTO_INCREMENT&#xff09;③非空約束…

HP Pavilion G6 筆記本安裝Ubuntu開機后自動進入飛行模式的問題解決

問題一臺HP Pavilion G6 筆記本 &#xff0c;安裝了Ubuntu24.04版本&#xff0c;開機后&#xff0c;直接進入飛行模式&#xff0c;導致無法使用Wifi,且使用fnf10的組合鍵&#xff0c;也無法關閉飛行模式。使用fnf10鍵&#xff0c;可以看到提示顯示飛行模式&#xff0c;但無法關…

LLM:MoE原理與實現探索

文章目錄前言一、Deepseek Moe二. Moe架構1. Expert2. Gate3. MoE Module三、Auxiliary Loss總結前言 MoE&#xff08;Mixture of Experts) 已經逐漸在LLM中廣泛應用&#xff0c;其工程部署相關目前也有了越來越多的支持&#xff0c;本文主要記錄一下MoE的基本模塊構造與原理。…

基于領域事件驅動的微服務架構設計與實踐

引言&#xff1a;為什么你的微服務總是"牽一發而動全身"&#xff1f; 在復雜的業務系統中&#xff0c;你是否遇到過這樣的困境&#xff1a;修改一個訂單服務&#xff0c;卻導致支付服務異常&#xff1b;調整庫存邏輯&#xff0c;用戶服務開始報錯。這種"蝴蝶效應…

如何使用curl編程來下載文件

libcurl 是一個功能強大的跨平臺網絡傳輸庫&#xff0c;支持多種協議。 本篇來介紹libcul的C語言編程&#xff0c;實現一個文件下載的功能。 1 curl基礎介紹 1.1 核心數據結構 1.1.1 CURL句柄 CURL是libcurl 的核心句柄&#xff0c;每個請求對應一個 CURL 實例&#xff0c;…

大語言模型提示工程與應用:ChatGPT提示工程技術指南

ChatGPT提示工程 學習目標 在本課程中&#xff0c;我們將學習更多關于ChatGPT的最新提示工程技術。 相關知識點 ChatGPT提示工程 學習內容 1 ChatGPT提示工程 ChatGPT是OpenAI研發的新型對話模型&#xff0c;具備多輪對話能力。該模型通過人類反饋強化學習(RLHF)訓練&am…

能力評估:如何系統評估你的技能和經驗

能力評估&#xff1a;如何系統評估你的技能和經驗 作為一名38歲的互聯網研發老兵&#xff0c;你已經積累了豐富的經驗&#xff0c;包括技術深度、項目管理、團隊協作等。但能力評估不是一次性事件&#xff0c;而是持續過程&#xff0c;幫助你識別優勢、短板&#xff0c;并為職業…