【圖論】單源最短路

前言

今天,我們來講最短路,首先看只有一個起點(單源)的情況。

為了書寫方便,我們約定以下內容:

template<class W>
using Graph = vector<vector<pair<int, W>>>;  // 鄰接表(vector實現)template<class W>
using AdjMatrix = vector<vector<W>>;   // 鄰接矩陣template<class W>
struct Edge{int u, v;W w;
};
  • 圖的頂點數記為n邊數記為m
  • 最短路的源點記為s
  • w(u,v)為邊(u,v)權值
  • d_u為點s到點u實際最短路長度。
  • dis_u為點s到點u估計最短路長度。任何時候都有dis_u \ge d_u,特別地,當最短路算法結束時,dis_u=d_u

Bellman-Ford 算法

Bellman–Ford 算法是一種基于松弛(relax)操作的最短路算法,可以求出有負權的圖的最短路,并可以對最短路不存在(有負環)的情況進行判斷。

過程

先介紹松弛操作:對于邊u\to v,松弛操作對應下式:

dis_v=min(dis_v,dis_u+w(u,v))

這么做很容易理解:嘗試用s\to u\to vs \to u取最短路徑)去更新v點最短路的長度,如果這條路徑更優,就進行更新。

我們不斷嘗試對圖上每一條邊進行松弛。每進行一輪循環,就對圖上所有的邊都松弛一遍,當一次循環中沒有成功的松弛操作時,算法停止。

那么,需要循環多少次呢?

如果最短路存在,那么每一次松弛都會讓最短路至少加上一條邊。而一條簡單路徑最多只有n-1條邊,所以最多松弛n-1輪就能得到最短路。

如果第n輪松弛操作仍然成功,那么說明從s出發,能到達一個負環。

綜上,時間復雜度為O(nm)

判負環的坑點

需要注意的是,跑Bellman-Ford算法時,如果沒有給出存在負環的結果,那么只能說明從s出發不能到達一個負環,不能保證圖上沒有負環(除了連通圖)。

所以,如果需要判斷圖上是否有負環,最嚴謹的做法是建立一個超級源點,向圖上每個節點連一條權值為0的邊,然后以超級源點為起點跑 Bellman–Ford

代碼

說明:返回的pair第一個元素表示圖是否無負環,第二個元素才是最短路數組。

// edge - 邊集
// n - 頂點數
// s - 源點
template<class W>
pair<bool, vector<W>> bellman(const vector<Edge<W>>& edge, int n, int s) {W inf = numeric_limits<W>::max();vector<W> dis(n, inf);dis[s] = 0;bool flag = false;for (int i = 0; i < n; i++) {flag = false;for (auto e : edge) {if (dis[e.u] == inf) continue;if (dis[e.v] > dis[e.u] + e.w) {dis[e.v] = dis[e.u] + e.w;flag = true;}}if (!flag) return make_pair(true, dis);}return make_pair(false, vector<W>());
}

SPFA 算法

Shortest Path Faster Algorithm,是對于 Bellman-Ford 的優化。

思路

很多時候,我們不需要那么多無用的松弛操作。

很顯然,只有上一次被松弛的結點,所連接的邊,才有可能引起下一次的松弛操作。

那么我們用隊列來維護哪些結點可能會引起松弛操作,就能只訪問必要的邊了。

如果要判負環,可以設cnt_u表示s \to u的最短路經過了多少條邊.

cnt_u \ge n時,說明從s點出發,可以抵達一個負環。

代碼

// G - 圖
// s - 源點
template<class W>
pair<bool, vector<W>> spfa(const Graph<W>& G, int s) {int n = G.size();W inf = numeric_limits<W>::max();queue<int> q;vector<W> dis(n, inf);vector<int> cnt(n, 0);vector<bool> vis(n, false);q.push(s);dis[s] = 0;vis[s] = true;while (q.size()) {int u = q.front();q.pop();vis[u] = false;for (auto [v, w] : G[u]) {if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;cnt[v] = cnt[u] + 1;if (cnt[v] >= n) return make_pair(false, vector<W>());if (!vis[v]) {vis[v] = true;q.push(v);}}}}return make_pair(true, dis);
}

Dijkstra 算法

過程

將節點分成兩個集合:

  • 已確定最短路長度的點集(記為S集合)。
  • 未確定最短路長度的點集(記為T集合)。

初始時,所有節點都屬于集合T

dis_s=0,其他點的dis\infty

接下來,重復以下操作:

  • T集合中,選擇一個dis最小的節點,移到S集合中。
  • 對這個節點的所有出邊進行松弛dis_v=min(dis_v,dis_u+w(u,v))

注意,帶負權的圖不能使用 Dijkstra 算法。

代碼

// G - 圖
// s - 源點
template<class W>
vector<W> dijkstra(const Graph<W>& G, int s) {int n = G.size();W inf = numeric_limits<W>::max();vector<W> dis(n, inf);vector<bool> vis(n, false);dis[s] = 0;for (int i = 0; i < n; i++) {int u = 0;W mind = inf;for(int j = 0; j < n; j++)if (!vis[j] && dis[j] < mind) {u = j;mind = dis[j];}vis[u] = true;for (auto [v, w] : G[u]) {dis[v] = min(dis[v], dis[u] + w);}}return dis;
}

優化

上述代碼時間復雜度是O(n^2),能否優化呢?

我們發現,只能優化找最小點的過程。

我們用一個小根堆來維護(按第一關鍵字)。初始時候插入(0,s),計算時,直接從堆頂取出的節點即是最優,然后彈出該節點。

松弛的時候,如果dis_v發生改變,那么就插入(dis_v,v)

時間復雜度O(m \log m)

代碼

template<class W>
vector<W> dijkstra(const Graph<W>& G, int s) {int n = G.size();W inf = numeric_limits<W>::max();priority_queue<pair<W, int>, vector<pair<W, int>>, greater<pair<W, int>>> q;vector<W> dis(n, inf);vector<bool> vis(n, false);dis[s] = 0;q.emplace(0, s);while (q.size()) {int u = q.top().second;q.pop();if (vis[u]) continue;vis[u] = true;for (auto& [v, w] : G[u]) {if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;q.emplace(dis[v], v);}}}return dis;
}

下一次,我們將學習多源最短路。?

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

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

相關文章

集中抄表電表是什么?

1.集中抄表電表&#xff1a;簡述 集中抄表電表&#xff0c;又稱為遠程抄表系統&#xff0c;是一種現代化電力計量技術&#xff0c;為提升電力行業的經營效率和客戶服務質量。它通過自動化的形式&#xff0c;取代了傳統人工抄水表&#xff0c;完成了數據信息實時、精確、高效率…

進制轉換【野路子改造】

非科班&#xff0c;一直都是自己的野路子&#xff0c;現在要回爐重造 十進制->二進制 基本思想&#xff1a; 開始寫的&#xff08;80%&#xff09;&#xff1a; #include<stdio.h> using namespace std; int main(){ int n; scanf("%d",&n); int a[1…

Spring -- DI

文章目錄 一、什么是DI二、注入的三種方式2.1 屬性注入 Autowired使用方法Autowired存在的問題以及解決方法Autowired問題的解決方法 2.2 構造方法注入2.3 setter方法注入2.4 三種注入方式優缺點分析 一、什么是DI 概念&#xff1a;DI(依賴注入)就是當我們把依賴對象取出來(創…

以太坊錢包

以太坊錢包是你通往以太坊系統的門戶。它擁有你的密鑰&#xff0c;并且可以代表你創建和廣播交易。選擇一個以太坊錢包可能很困難&#xff0c;因為有很多不同功能和設計選擇。有些更適合初學者&#xff0c;有些更適合專家。即使你現在選擇一個你喜歡的&#xff0c;你可能會決定…

mac m1 pcre.h 找不到

安裝suricata報錯&#xff1a; configure: error: pcre.h not found ... 解決&#xff1a; brew install pcre 找到這個文件的地址 brew list pcre | grep pcre.h$ /opt/homebrew/Cellar/pcre/8.45/include/pcre.h 程序搜索的地址 cpp -v /Library/Developer/CommandLineT…

5.26 基于UDP的網絡聊天室

需求&#xff1a; 如果有人發送消息&#xff0c;其他用戶可以收到這個人的群聊信息 如果有人下線&#xff0c;其他用戶可以收到這個人的下線信息 服務器可以發送系統信息實現模型 模型&#xff1a; 代碼&#xff1a; //chatser.c -- 服務器端實現 #include <stdio.h>…

hive初始化失敗報錯:Error: Duplicate key name ‘PCS_STATS_IDX‘ (state=42000,code=1061)

意思是key name ‘PCS_STATS_IDX’ (state42000,code1061)重復了&#xff0c;問題出在不是第一次初始化&#xff0c;因為我們在hive-site.xml中配置了 javax.jdo.option.ConnectionURL jdbc:mysql://192.168.200.137:3306/metastore?createDatabaseIfNotExisttrue JDBC conne…

JavaSE——類和對象(二)~~封裝

目錄 一.封裝 二.封裝擴展之包 三.static成員 四. 代碼塊 五. 內部類&#xff08;重要&#xff09; 大家好呀&#xff0c;我是北緯&#xff0c;接著上節我們繼續講解Java中關于類和對象的相關知識&#xff0c;今天著重給大家介紹一下關于面向對象程序的特性之一——封裝。…

【Linux】常用基礎命令 | 搭建云服務器優化環境 | 程序的部署

文章目錄 Linux常用命令及搭建環境一、LinuxLinux發行版 1.常用命令1.ls2.cd3.pwd4.touch5.cat6.echo7.vim8.mkdir9.rm10.mv11.cp12.man13.grep14.ps15.netstat 2.搭建Java Web程序的運行環境包管理器1.安裝JDK2.安裝Tomcat3.安裝mysql 3.程序的部署 Linux常用命令及搭建環境 …

貪心算法簡單介紹

貪心算法是一種在每一步選擇中都采取當前狀態下最優或最優近似的選擇&#xff0c;以期望最終得到全局最優解的算法。貪心算法并不總能得到全局最優解&#xff0c;但在某些問題上&#xff0c;它可以得到全局最優解&#xff0c;并且比動態規劃等其他方法更為簡單和高效。 貪心算…

Python庫之Scrapy的簡介、安裝、使用方法詳細攻略

Python庫之Scrapy的簡介、安裝、使用方法詳細攻略 簡介 Scrapy是一個快速的、高層次的web抓取和web抓取框架&#xff0c;用于抓取網站數據并從頁面中提取結構化的數據。Scrapy用途廣泛&#xff0c;可以用于數據挖掘、信息處理或存儲歷史數據&#xff0c;以及各種其他用途。 …

【AMS】Android 8.0+ 繞開啟動后臺Service限制

一、背景 應客戶要求,需要在開機時,拉起應用A。但因為開機時,同時被拉起的應用過多,導致Launcher在開機那一刻較為卡頓。為解決這一問題,采取了延遲拉起的做法。在開機后,延遲一定時間,由系統服務,拉起應用A。 于是乎,就出現這么個報錯: Not allowed to start ser…

vue3、vuex和vue-router入門指南

Vue 3、Vuex 和 Vue Router 都是 Vue.js 生態系統中非常有用的庫。它們各自在 Vue.js 應用程序中扮演著重要的角色&#xff1a;Vue 3 是核心框架&#xff0c;Vuex 用于狀態管理&#xff0c;而 Vue Router 用于路由管理。下面是如何在 Vue 3 項目中使用這些庫的簡要說明。 創建…

有趣的css - 移形換位加載動畫

大家好&#xff0c;我是 Just&#xff0c;這里是「設計師工作日常」&#xff0c;今天分享的是一個移形換位動態加載小動效&#xff0c;適用于 app 列表加載&#xff0c;頁面加載或者圖片懶加載等場景。 最新文章通過公眾號「設計師工作日常」發布。 目錄 整體效果核心代碼html…

2024上海初中生古詩文大會倒計時4個月:單選題真題解析(持續)

現在距離2024年初中生古詩文大會還有4個多月時間&#xff0c;我們繼續來看10道選擇題真題和詳細解析&#xff0c;以下題目截取自我獨家制作的在線真題集&#xff0c;都是來自于歷屆真題&#xff0c;去重、合并后&#xff0c;每道題都有參考答案和解析。 為幫助孩子自測和練習&…

C#基礎一

使用Visual Studio 2022&#xff08;VS2022&#xff09;編寫C#控制臺程序 1. 安裝Visual Studio 2022 確保已安裝Visual Studio 2022。如果未安裝&#xff0c;請從Visual Studio官網下載并安裝。 另一篇文章中已經有詳細描述&#xff0c;這里就不在細說了。 VisualStudio2022…

【LeetCode】【209】長度最小的子數組(1488字)

文章目錄 [toc]題目描述樣例輸入輸出與解釋樣例1樣例2樣例3 提示進階Python實現前綴和二分查找滑動窗口 個人主頁&#xff1a;丷從心 系列專欄&#xff1a;LeetCode 刷題指南&#xff1a;LeetCode刷題指南 題目描述 給定一個含有n個正整數的數組和一個正整數target找出該數組…

Effective C++(2)

文章目錄 2. 構造、析構、賦值運算條款05&#xff1a;了解C默默編寫并調用哪些函數條款06&#xff1a;若不想使用編譯器自動生成的函數&#xff0c;就該明確拒絕條款07&#xff1a;為多態基類聲明virtual析構函數條款08&#xff1a;別讓異常逃離析構函數條款09&#xff1a;絕不…

微信小程序報錯:notifyBLECharacteristicValueChange:fail:nodescriptor的解決辦法

文章目錄 一、發現問題二、分析問題二、解決問題 一、發現問題 微信小程序報錯&#xff1a;notifyBLECharacteristicValueChange:fail:nodescriptor 二、分析問題 這個提示有點問題&#xff0c;應該是該Characteristic的Descriptor有問題&#xff0c;而不能說nodescriptor。 …

web前端之解決img元素組件自有高度的問題

MENU 前言解決辦法vertical-align 前言 在HTML和CSS中&#xff0c;img元素默認是行內元素(inline element)&#xff0c;類似于文本。由于文本有基線(baseline)&#xff0c;所以即使是空白的img元素也會占據一定的高度&#xff0c;以便使基線對齊。 解決辦法 要解決這個問題&…