日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

最短路径(Dijkstra、Bellman-Ford和SPFA算法)

發(fā)布時間:2025/3/19 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最短路径(Dijkstra、Bellman-Ford和SPFA算法) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

最短路徑(Dijkstra、Bellman-Ford和SPFA算法)

  • 前言
  • 圖的存儲方式
    • 鄰接矩陣
    • 鄰接表
      • 鏈表建立
      • 利用vector
      • 結(jié)構(gòu)體
  • 核心思路
  • Dijkstra算法
    • 圖解
    • 基本思想
    • 求解步驟
    • 細(xì)節(jié)解釋
      • 變量的意義
      • 初始化
      • 迭代次數(shù)
      • 更新條件
    • 完整代碼
  • Bellman-Ford算法
    • 思路:
    • 變量說明
    • 初始化
    • 完整代碼
  • Bellman-Ford 檢測圖是否含有負(fù)圈(回路)
    • 思路
    • 代碼
  • Bellman-Ford 算法優(yōu)化
  • SPFA算法
    • 思路
    • 完整代碼
  • 算法的比較

前言

提醒一下:最短路徑一般用于有向網(wǎng)(就是有方向和權(quán)值的圖),而最小生成樹一般是用于無向網(wǎng)。
本來是想分開寫,不過想到這樣大家看起來比較麻煩。就寫在一起了,這樣可以很好的進(jìn)行對比。

圖的存儲方式

常用的無非就

  • 鄰接矩陣
  • 鄰接表
  • 鏈?zhǔn)角跋蛐?/li>

    鄰接矩陣

    這個可以說是最好理解的了,分為以下幾種情況

    • 無權(quán)值得圖,初始化為0,有邊想連(有關(guān)系)就賦值為1
    • 有權(quán)值圖(網(wǎng)),初始化為 INF(一個大的常數(shù)),有邊想連(有關(guān)系)就賦值為 該權(quán)值

    鄰接表

    可以說鄰接表的建圖就很花里胡哨了,如果不理解它的意思,肯定被每一個人都不一樣的鄰接表給弄糊涂的。為了讓大家看下邊的代碼不亂,特意說明一下。

    鏈表建立

    • 對于鏈表,就是一個一維數(shù)組,其中存放表頭節(jié)點(diǎn),每一個表頭節(jié)點(diǎn)都連著一個鏈表,是由其相鄰的頂點(diǎn)組成。

    利用vector

    基于鏈表中的特性,可以用一個vector數(shù)組來代替鏈表

    • 如果是不帶權(quán)值的
    vector<int>edge[N];int main() {int V, E;cin >> V >> E;for (int i = 0; i < E; ++i){int u,v;cin >> u >> v;edge[u].push_back(v);/*如果是無向圖edge[u].push_back(v);edge[v].push_back(u);*/}// 遍歷,通過頂點(diǎn)來遍歷,for (int i = 1; i <= V; ++i){for (int j = 0; j < edge[i].size(); ++j)cout << edge[i][j] << " ";}}
    • 如果有權(quán)值的話,有兩個地方改就好
    #define pi pair<int,int> vector<pi>edge[N];
    • 存的時候,
    for (int i = 0; i < E; ++i){int u,v,w;cin >> u >> v>>w;edge[u].push_back(make_pair(v,w));/*如果是無向圖edge[u].push_back(make_pair(v,w));edge[v].push_back(make_pair(u,w));*/}

    結(jié)構(gòu)體

    結(jié)構(gòu)體的話就想到與用邊來作為一個頭來建立

    //從頂點(diǎn)u到頂點(diǎn)v的權(quán)值為w的邊 struct edge {int u, v, w; }; edge es[N];//儲存所有邊int main() {int V, E;cin >> V >> E;for (int i = 1; i <= E; ++i){int u, v,w;cin >> u >> v>>w;es[i] = edge{ u, v, w };/*如果是無向圖es[i] = edge{ u, v, w };es[i] = edge{ v, u, w };*/}// 遍歷,通過邊來遍歷,for (int i = 1; i <= E; ++i){edge e = es[i];cout << e.u << " " << e.v << " " << e.w << endl;}} 相信說到這,就可以慢慢悟了把

    核心思路

    本來想放在后頭,不過怕大家一路看過去直接迷了。就放在前邊,可以帶著這個去看.

    Dijkstra算法

    // dis[k] 中 dis[k] 表示源點(diǎn)到 頂點(diǎn)k的距離(直接到k) //dis[mark] + g[mark][k] 先到當(dāng)前距離短的點(diǎn)mark,通過這個點(diǎn)轉(zhuǎn)到k dis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距離

    Bellman-Ford和SPFA算法

    if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;

    其實(shí)大家理解和這兩個是一個道理。都可以通過下面這段話理解。

    可以想一下有三個點(diǎn),1,2,3.剛開始1到3距離為6,1到2是3,2到3是2.以1為源點(diǎn),因?yàn)榈巾旤c(diǎn)2的距離小,所以將2加入。這個時候在更新距離時,之前沒有加入2的時候1到3只有1–>3這個路徑可以走,而現(xiàn)在2加入后,有新的路徑了1–>2–>3,我們在算一下長度,哇才是5,比直接走到3小,好那么我們就改變路徑。這就是上面代碼的意義。

    Dijkstra算法

    圖解

    由于本博主畫的圖太,借了點(diǎn)圖

    ----- S是已計(jì)算出最短路徑的頂點(diǎn)的集合
    ----- U是未計(jì)算出最短路徑的頂點(diǎn)的集合
    ----- C(3)表示頂點(diǎn)C到起點(diǎn)D的最短距離為3

  • 選擇頂點(diǎn)D
    S={D(0)}
    U={A(∞), B(∞), C(3), E(4), F(∞), G(∞)}
  • 選取頂點(diǎn)C
    S={D(0), C(3)}
    U={A(∞), B(13), E(4), F(9), G(∞)}

    就這樣當(dāng)作一個引子
  • 基本思想

    把帶權(quán)圖中的所有頂點(diǎn)V分成兩個集合S和T,S中存放已經(jīng)確定最短路徑的頂點(diǎn),初始時,S中僅包含一個源點(diǎn); T=V?S,存儲待確定最短路徑的頂點(diǎn),初始時,T中包含除源點(diǎn)外的頂點(diǎn),此時各頂點(diǎn)的當(dāng)前最短路徑長度為源點(diǎn)到各頂點(diǎn)的弧上的權(quán)值。開始操作時,從T中選取當(dāng)前最短路徑長度最小的一個頂點(diǎn) Vi 加入S,然后修改T中剩余頂點(diǎn)的當(dāng)前最短路徑長度,修改的原是:當(dāng) Vi 的最短路徑長度與 Vi 到T中的頂點(diǎn)之間的權(quán)值之和小于該頂點(diǎn)的當(dāng)前路徑長度時,用前者替換后者。重復(fù)上述過程,直到S中包含所有的頂點(diǎn)
    為止。

    簡單來說,將點(diǎn)分為兩個集合,將源點(diǎn)放入一個集合中,其余的點(diǎn)放入另一個集合。(實(shí)現(xiàn)算法不需要用到集合,可以用一個bool 數(shù)組來記錄,為true就表明就加入到了源點(diǎn)所在的集合中)。然后遍歷不在源點(diǎn)集合的點(diǎn),找到距離源點(diǎn)最近的點(diǎn)。將這個點(diǎn)加入到源點(diǎn)所在的集合中。然后以這個點(diǎn)為中轉(zhuǎn)點(diǎn)更新源點(diǎn)到其余點(diǎn)的距離。

    求解步驟

  • 將有向網(wǎng)用鄰接矩陣儲存,沒有相連的邊用一個較大的數(shù)表示。
  • 找到距離源點(diǎn)最近的點(diǎn),將其加入到源點(diǎn)所在的集合中。
  • 以這個點(diǎn)為中轉(zhuǎn)點(diǎn),更新源點(diǎn)到其余點(diǎn)的距離。
  • 細(xì)節(jié)解釋

    變量的意義

    int g[N][N];//鄰接矩陣存圖,g[i][j] 表示 頂點(diǎn)i帶頂點(diǎn)j的距離 int dis[N];//表示 源點(diǎn)src到 i的最短距離 bool vis[N];//是否在集合中 int stc;//源點(diǎn) int n, m;//n 頂點(diǎn)數(shù),m邊數(shù)

    初始化

    void init() {mst(g, 0);mst(vis, 0); //初始化點(diǎn)的集合為空mst(dis, INF);//初始化距離為一個很大的數(shù) }

    迭代次數(shù)

    for (int i = 1; i < n; ++i) //n 個點(diǎn),只用迭代n-1次就好 要明確算法的結(jié)束標(biāo)識是:將所有點(diǎn)加入到源點(diǎn)所在的集合,這樣就好理解了。

    因?yàn)閯傞_始就將源點(diǎn)放入集合中了,所要就還需要n-1次

    更新條件

    這就是神秘的松弛操作了 // dis[k] 中 dis[k] 表示源點(diǎn)到 頂點(diǎn)k的距離(直接到k) //dis[mark] + g[mark][k] 先到當(dāng)前距離短的點(diǎn)mark,通過這個點(diǎn)轉(zhuǎn)到k dis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距離

    可以想一下有三個點(diǎn),1,2,3.剛開始1到3距離為6,1到2是3,2到3是2.以1為源點(diǎn),因?yàn)榈巾旤c(diǎn)2的距離小,所以將2加入。這個時候在更新距離時,之前沒有加入2的時候1到3只有1–>3這個路徑可以走,而現(xiàn)在2加入后,有新的路徑了1–>2–>3,我們在算一下長度,哇才是5,比直接走到3小,好那么我們就改變路徑。這就是上面代碼的意義。

    完整代碼

    #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<string> #include<queue> #include<map> #include<stack> #include<set> //#include<unordered_map> #include<ctime> using namespace std; typedef long long ll; #define pi pair<int,int> #define mst(ss,b) memset(ss,b,sizeof(ss)); #define rep(i,k,n) for(int i=k;i<=n;i++) #define INF 0x3f3f3f3f const int N = 2e3 + 10;int g[N][N];//鄰接矩陣存圖,g[i][j] 表示 頂點(diǎn)i帶頂點(diǎn)j的距離 int dis[N];//表示 源點(diǎn)src到 i的最短距離 bool vis[N];//是否在集合中 int stc;//源點(diǎn) int n, m;//n 頂點(diǎn)數(shù),m邊數(shù) void init() {mst(g, 0);mst(vis, 0); //初始化點(diǎn)的集合為空mst(dis, INF);//初始化距離為一個很大的數(shù) }void Dijkstra() {dis[stc] = 0;//自己到自己的距離肯定為1vis[stc] = 1;//把源點(diǎn)存入集合中for (int i = 1; i < n; ++i) //n 個點(diǎn),只用迭代n-1次就好{int mark = stc, midis = INF;int j;for ( j = 0; j < n; ++j){if (!vis[j] && dis[j] < midis)//從沒有加入的點(diǎn)中{//尋找一個最小值mark = j;midis = dis[j];}}// mark 就是找到的 不在集合內(nèi)部且當(dāng)前路徑長度(與源點(diǎn)的距離)最小的頂點(diǎn)if (mark != stc) //如果不是本身vis[mark] = 1; //將點(diǎn)j加入集合//對不在集合中的頂點(diǎn)修改dis[j]的值for (int k = 0; k < n; ++k){if (!vis[k])// dis[k] 中 dis[k] 表示源點(diǎn)到 頂點(diǎn)k的距離(直接到k)//dis[mark] + g[mark][k] 先到當(dāng)前距離短的點(diǎn)mark,通過這個點(diǎn)轉(zhuǎn)到kdis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距離}} }

    Bellman-Ford算法

    思路:

    要讓以下等式成立
    dis[v]=min(dist[v],dis[u]+e(u,v)∣(u,v)∈Edis[v] = min ({dist[v],dis[u]+e(u,v)|(u,v)\in E} dis[v]=min(dist[v],dis[u]+e(u,v)(u,v)E
    其中 v是終點(diǎn),u是起點(diǎn)。

    變量說明

    //從頂點(diǎn)u到頂點(diǎn)v的權(quán)值為w的邊 struct edge {int u,v,w; } edge es[maxn] ;//儲存所以邊 int dis[maxn]; //最短距離 int V,E; //頂點(diǎn)數(shù)、邊數(shù)

    初始化

    如果s是源點(diǎn)(起點(diǎn))則dis[s] = 0,之外,其余為INF(足夠大的常數(shù))

    完整代碼

    #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<string> #include<queue> #include<map> #include<stack> #include<set> //#include<unordered_map> #include<ctime> using namespace std; typedef long long ll; #define pi pair<int,int> #define mst(ss,b) memset(ss,b,sizeof(ss)); #define rep(i,k,n) for(int i=k;i<=n;i++) #define INF 0x3f3f3f3fconst ll maxn = 2e5 + 10; //從頂點(diǎn)u到頂點(diǎn)v的權(quán)值為w的邊 struct edge {int u, v, w; }; edge es[maxn];//儲存所有邊 int dis[maxn]; //最短距離 int V, E; //頂點(diǎn)數(shù)、邊數(shù)//求解從頂點(diǎn)s出發(fā)到所以點(diǎn)的最短距離void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;//對每一條邊進(jìn)行V-1次松弛for (int i = 0; i < V; ++i){for (int j = 0; j < E; ++j){edge e = es[j];if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;}}}
    • 為什么是V-1次呢?
      因?yàn)橐粋€含有n個頂點(diǎn)的圖中,任意兩個頂點(diǎn)之間的最短路徑最多包含n-1條邊,

    Bellman-Ford 檢測圖是否含有負(fù)圈(回路)

    思路

    如果進(jìn)行了V-1 次(V為頂點(diǎn)數(shù))松弛后還可以進(jìn)行松弛就表明存在負(fù)圈

    代碼

    在上面的Bellman-Ford基礎(chǔ)上,

    #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<string> #include<queue> #include<map> #include<stack> #include<set> //#include<unordered_map> #include<ctime> using namespace std; typedef long long ll; #define pi pair<int,int> #define mst(ss,b) memset(ss,b,sizeof(ss)); #define rep(i,k,n) for(int i=k;i<=n;i++) #define INF 0x3f3f3f3fconst ll maxn = 2e5 + 10; //從頂點(diǎn)u到頂點(diǎn)v的權(quán)值為w的邊 struct edge {int u, v, w; }; edge es[maxn];//儲存所以邊 int dis[maxn]; //最短距離 int V, E; //頂點(diǎn)數(shù)、邊數(shù)//求解從頂點(diǎn)s出發(fā)到所以點(diǎn)的最短距離void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;//對每一條邊進(jìn)行V-1次松弛for (int i = 0; i < V; ++i){for (int j = 0; j < E; ++j){edge e = es[i];if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;}}}bool neg_cir() {bool flag = false;for (int i = 0; i < E; ++i){edge e = es[i];if (dis[e.v] > dis[e.u] + e.w){flag = true;break;}}return flag; }

    Bellman-Ford 算法優(yōu)化

    在實(shí)際操作中,Bellman-Ford 算法經(jīng)常會未達(dá)到 n - 1輪松弛前就已經(jīng)計(jì)算出所有最短路,之前我們已經(jīng)說過,n - 1其實(shí)是最大值。因此我們需要變量判斷一下下輪循環(huán) dis中的值是否改變,可以提前跳出循環(huán),提高效率,基本優(yōu)化如下:

    void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;while (true){bool update = false; //檢測是否有更新for (int i = 0; i < E; ++i){edge e = es[i];//如果起點(diǎn)到 e.u 距離為 INF 就說明無法松弛了if (dis[e.u] != INF && dis[e.v] > dis[e.u] + e.w) {dis[e.v] = dis[e.u] + e.w;update = true;}}//沒有的話就退出循環(huán)if (!update)break;}}

    SPFA算法

    思路

    SPPA其實(shí)是Bellman-Ford的隊(duì)列優(yōu)化。我們用數(shù)組dits比錄每個結(jié)點(diǎn)的最短路徑估計(jì)值,并用鄰接表來存儲圖g。我們采取的方法是松弛:設(shè)立一個先進(jìn)先出的隊(duì)列用來保存待優(yōu)化的結(jié)點(diǎn),優(yōu)化時每次取出隊(duì)首結(jié)點(diǎn)u,并且用u點(diǎn)當(dāng)前的最短路徑估計(jì)值對u點(diǎn)所指向的結(jié)點(diǎn)v進(jìn)行松弛操作,如果v點(diǎn)的最短路徑估計(jì)值有所調(diào)整,且v點(diǎn)不在當(dāng)前的隊(duì)列中,就將v點(diǎn)放入隊(duì)尾。這樣不斷從隊(duì)列中取出結(jié)點(diǎn)來進(jìn)行松弛操作,直至隊(duì)列空為止。

    只要最短路徑存在,上述SPFA算法必定能求出最小值。因?yàn)槊看螌Ⅻc(diǎn)放入隊(duì)尾,都是經(jīng)過松弛操作達(dá)到的。換言之,每次的優(yōu)化將會有某個點(diǎn)p的最短路徑估計(jì)值d[9]變小。所以算法的執(zhí)行會使d越來越小。由于我們假定圖中不存在負(fù)權(quán)回路,所以每個結(jié)點(diǎn)都有最短路徑值。因此,算法不會無限執(zhí)行下去,隨著d值的逐漸變小,直到到達(dá)最短路徑值時,算法結(jié)束,這時的最短路徑估計(jì)值就是對應(yīng)結(jié)點(diǎn)的最短路徑值。

    完整代碼

    #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<string> #include<queue> #include<map> #include<stack> #include<set> //#include<unordered_map> #include<ctime> using namespace std; typedef long long ll; #define pi pair<int,int> #define mst(ss,b) memset(ss,b,sizeof(ss)); #define rep(i,k,n) for(int i=k;i<=n;i++) #define INF 0x3f3f3f3f const int N = 2e3 + 10;//也可以用結(jié)構(gòu)體 vector<pi> g[N];//鄰接表儲存所以邊,// g[i][j].first 表示i節(jié)點(diǎn)的第j條邊的編號(本來鄰接表的作用)//g[i][j].second 表示邊的長度,權(quán)值 int dis[N];//表示 源點(diǎn)src到 i的最短距離 bool vis[N];//是否在集合中 int src;//源點(diǎn) int n, m;//n 頂點(diǎn)數(shù),m邊數(shù) queue<int>q;void init() {for (int i = 0; i <= n; ++i)g[i].clear();mst(vis, 0); //初始化點(diǎn)的集合為空mst(dis, INF);//初始化距離為一個很大的數(shù)while (!q.empty())q.pop(); //將隊(duì)列清空 }void spfa() {init();//記得初始化dis[src] = 0;//自己到自己的距離肯定為1vis[src] = 1;//把源點(diǎn)存入集合中q.push(src); //將源點(diǎn)壓入隊(duì)列while (!q.empty()){int u = q.front(); //取出對首元素q.pop();for (int i = 0; i < g[u].size(); ++i){//這個語句和 Dijstra 中的// dis[k] 中 dis[k] 表示源點(diǎn)到 頂點(diǎn)k的距離(直接到k)//dis[mark] + g[mark][k] 先到當(dāng)前距離短的點(diǎn)mark,通過這個點(diǎn)轉(zhuǎn)到k//意思是一樣的if (dis[u] + g[u][i].second < g[u][i].first){//如果更小就更新g[u][i].first = dis[u] + g[u][i].second;if (!vis[g[u][i].first]) //不在集合中{//就加入vis[g[u][i].first] = 1;q.push(g[u][i].first);}}}//讓這個點(diǎn)還有進(jìn)入隊(duì)列的機(jī)會vis[u] = false;} }

    算法的比較

    總結(jié)

    以上是生活随笔為你收集整理的最短路径(Dijkstra、Bellman-Ford和SPFA算法)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。