演算法訓練 最短路

2021-08-15 09:38:53 字數 3496 閱讀 2947

最近在學習一些常見的演算法,演算法這東西不練是不行的,所以學習的同時也找了些題來練。我找的題目**於 vjudge 上的 kuangbin帶你飛專題訓練。每做完乙個專題,我都會寫一篇部落格整理一下這個專題。現在這個專題是,圖論中的最短路。

最短路相關的演算法有很多,在專題訓練中會用到的有 dijkstra 演算法(包括優先佇列優化版本) 、bellman-ford 演算法(包括其佇列優化版本 spfa 演算法)、floyd-warshall 演算法。以下就將主要介紹這幾個演算法,並提供**模板。

dijkstra 演算法是很多資料結構和演算法教材上都介紹過的一種最短路演算法,它的思想主要是乙個貪心的思想,即每一次選擇距離源節點最近的節點作為拓展節點,最多經過 v(節點數)次選擇,就可以確定從源節點到所有節點的最短路徑。

下面是普通的 dijkstra 演算法和經過優先佇列優化後的 dijkstra 演算法的**:

普通版

int v, d[maxv];

void dijkstra(int s)

if (v == -1)

break;

used[v] = true;

// 從拓展節點開始向與其相鄰的節點鬆弛

for (int u = 0;u < v;u++)

if (!used[u])

d[u] = min(d[u], d[v]+cost[v][u]);

}}

優先佇列版

struct node

bool

operator

< (const node &p) const

};struct edge

}am[maxv];

int v, d[maxv];

void pq_dijkstra(int s)}}

}

bellman-ford 演算法基於以下遞推公式: d[

i]=m

in(d

[j]+

cost

[j][

i])

bellman-ford 演算法可以處理帶有負邊的情況,條件是到某個節點的最短路徑在第 n 次迴圈中仍然被更新。

普通版

struct edgees[maxe];

int v, e;

int d[maxv];

void bellman-ford()

}if (!update)

break;

}}

判負環

// 使用的資料結構和上面的**相同

// 如果返回值為 true 則說明存在負環

bool find_negative_loop()}}

return

false;

}

bellman-ford 演算法有乙個明顯的缺陷是對於乙個 d[i],即便 d[i] 並不是到達 i 節點的最短路徑長度,與 i 相連的部分節點 j 還是會被鬆弛,這樣經過鬆弛之後的 d[j] 也不是最短路,所以 bellman-ford 演算法可以在這個方面被優化。spfa 演算法就是乙個被廣泛使用的 bellman-ford 演算法佇列優化版本,spfa 演算法也具備判斷負環是否存在的能力,判斷條件是如果某個節點入隊次數超過 v 次,那麼它一定在某乙個負環上。

struct edgeam[maxe];

int ei, head[maxn];

void init()

void add(int u, int v, int p)

// 以上資料結構鄰接表,spfa 演算法中大多使用鄰接表

int v, e;

int d[maxv], cnt[maxv];

bool used[maxv];

void spfa(int s)}}

}}

以上是 spfa 演算法的佇列版本,還有相應的棧版本,有的情況下用棧會更快。此外,spfa 演算法還有 dfs 形式。判斷負環的時候,用 spfa 的 dfs 形式判負環的平均時間複雜度相比於 bfs 形式更低一些。

floyd-warshall 演算法是用來求任意兩點之間的最短路徑的,它將動態規劃思想應用於求最短路的乙個演算法。

設 d[k][i][j] 表示從 i 點 經過 k 點 到 j 點的最短路徑長度,那麼它可以分成兩種情況:

1、不經過點 k 的路徑到達 j 點;

2、經過點 k 一次的路徑到達 j 點(如果不存在負環的話,對於任意點 k ,從 i 到 j 的最短路徑最多隻經過 k 一次)

因此就可以得到以下遞推公式: d[

k][i

][j]

=min

(d[k

−1][

i][j

],d[

k−1]

[i][

k]+d

[k−1

][k]

[j])

值得注意的是,對於 d[k][i][j] 而言,k 的範圍是 0~k,然而經過上述歸約之後,範圍變到了 0~k-1。

可以看到經過點 k 的最短路只和經過 k-1 的最短路相關,因此可以將以上遞推公式降為二維的形式: d[

i][j

]=mi

n(d[

i][j

],d[

i][k

]+d[

k][j

])floyd-warshall 演算法同樣可以用來判負環,條件是如果 d[i][i] < 0, 那麼點 i 一定位於某個負環上。

// floyd-warshall 演算法

int v, d[maxv][maxv]; // d[u][v] 表示 u 到 v 之間的路徑

void floyd_warshall()

最短路中有一類重要的題目形式就是差分約束題,差分約束系統將求解乙個不等式組中兩個變數的最大值問題變為最短路問題,不得不說我個人覺得還是蠻神奇的…

對於具有 n 個變數 m 個不等式的不等式組,如果不等式組中每個不等式左右兩邊都只有乙個變數,那麼這種不等式方程組又叫做差分約束系統。對於這樣的不等式組中的不等式,有以下兩種情況:

1、 xi+

c>=xj

對應一條從 i 節點到 j 節點的邊權為 c 的路徑

2、 xi+

c<=xj

對應一條從 j 節點到 i 節點的邊權為 -c 的路徑

依照以上兩條規則建圖,求 xj

−xi 的最大值就等價於求點 i 到 j 的最短路。

另外,在某些最短路問題中,還發現一些小技巧。

比如說,求 s 到其餘所有點的最短距離是用最短路演算法跑一遍,求其餘所有點到 s 的最短距離的話,就把對應的鄰接矩陣轉置之後,用最短路演算法跑一遍。

在比如說,在 hdu-4725 題中,建圖需要先拆點,把每個層都拆成兩個點,乙個進入點乙個出來的點。這樣的想法我覺得很妙,這樣的拆點建圖的方法,在另外一些題目中還有用到。

演算法訓練 最短路

問題描述 給定乙個n個頂點,m條邊的有向圖 其中某些邊權可能為負,但保證沒有負環 請你計算從1號點到其他點的最短路 頂點從1到n編號 輸入格式 第一行兩個整數n,m。接下來的m行,每行有三個整數u,v,l,表示u到v有一條長度為l的邊。輸出格式 共n 1行,第i行表示1號點到i 1號點的最短路。樣例...

演算法訓練 最短路

問題描述 給定乙個n個頂點,m條邊的有向圖 其中某些邊權可能為負,但保證沒有負環 請你計算從1號點到其他點的最短路 頂點從1到n編號 輸入格式 第一行兩個整數n,m。接下來的m行,每行有三個整數u,v,l,表示u到v有一條長度為l的邊。輸出格式 共n 1行,第i行表示1號點到i 1號點的最短路。樣例...

演算法訓練 最短路

演算法訓練 最短路 時間限制 1.0s 記憶體限制 256.0mb 問題描述 給定乙個n個頂點,m條邊的有向圖 其中某些邊權可能為負,但保證沒有負環 請你計算從1號點到其他點的最短路 頂點從1到n編號 輸入格式 第一行兩個整數n,m。接下來的m行,每行有三個整數u,v,l,表示u到v有一條長度為l的...