最短路演算法之:floyd 演算法

Weekoder發表於2024-06-09

最短路系列:floyd 演算法

大家好,我是Weekoder!

最近學了最短路,我來出一個最短路系列辣!

今天的演算法是:floyd 演算法!

我會用我自己的理解儘量讓大家搞懂 floyd 演算法。

floyd 演算法的用處

floyd 演算法是最短路演算法之一,適合求解多源最短路徑問題。什麼是多源最短路徑呢?其實就是起點和終點都有多個的最短路徑演算法,在計算完之後可以以 \(O(1)\) 的速度獲得任意兩點之間的最短路徑。

另外,floyd 演算法的本質其實是動態規劃

floyd 演算法的思想

在圖中,我們往往需要求出某個點到另一個點的最短路徑,這時候我們就可以使用 floyd 演算法。

我們來想一想該怎麼用最直接的方式求出兩點之間的最短路徑。假設城市 A 和城市 B 之間的距離是 \(10\),那我們首先可以想到直接從 A 走到 B,距離為 \(10\)。這時候,如果有一個城市 C,A 到 C 的距離是 \(3\),C 到 B 的距離是 \(5\),我們就可以先來到城市 C,再前往城市 B。這樣的總距離是 \(3+5=8\),比之前的 \(10\) 要更優。

那到底為什麼會更優呢?是因為有了城市 C 這樣一個中轉站,使得總距離縮短了。我們仔細思考一下,在圖上兩點之間的直接距離往往不是最優的,而是要經過一些中轉站,使得總路程進一步縮短。floyd 演算法實際上就是列舉了這些中轉站,並用狀態轉移方程求解。

floyd 演算法的實現

我們知道了要使兩點之間的距離縮短,就必須引入中轉站。現在,我們來看一下 floyd 演算法是怎麼實現的。

先看部分程式碼如下:

for (int k = 1; k <= n; k++)
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			// 狀態轉移方程 

可以看到,這裡有三層迴圈。

  1. 第一層迴圈的 \(k\) 是在列舉每個中轉站。

  2. 第二層迴圈的 \(i\) 是在列舉起點。

  3. 第三層迴圈的 \(j\) 是在列舉終點。

最後在迴圈裡執行狀態轉移方程。

有一個重點:中轉站必須在最外層列舉!我們要列舉中轉站逐個更新,如果在外層列舉起點和終點,會導致更新不完全

那麼,狀態轉移方程又是什麼呢?

我們只需要在直接到達的路徑和間接到達的路徑中取 min 就行了:

\[e_{i,j}=\min(e_{i,j},e_{i,k}+e_{k,j}); \]

這裡的 \(e_{i,j}\) 表示 \(i\) 點到 \(j\) 點目前的最短路徑。

狀態轉移方程用人話講就是在 \(i\) 直接到 \(j\)\(i\) 先到 \(k\) 再從 \(k\)\(j\) 中取 min。

接下來講一下二維陣列 \(e\) 的初始化。

首先,我們知道一個點到自己的距離是 \(0\),所以有:

\[e_{i,i}=0; \]

而其他的數我們可以先設為 INF(無窮大),所以又有:

\[e_{i,j}=+\infty,\space i\ne j; \]

而當我們輸入一條邊時,一般會有三個引數 \(u,v,w\),代表從 \(u\)\(v\) 有一條權值為 \(w\) 的邊,無向邊構建如下:

\[e_{u,v}=e_{v,u}=w; \]

有向邊構建如下:

\[e_{u,v}=w; \]

例題實踐

  1. 難度一顆星:B3647 【模板】Floyd

Code:

#include <bits/stdc++.h> 
using namespace std;
const int N = 105, INF = 1 << 30;
int n, m, e[N][N];
void floyd() {
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				if (e[i][k] != INF && e[k][j] != INF)
					e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			e[i][j] = i == j ? 0 : INF;
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		e[u][v] = e[v][u] = w;
	}
	floyd();
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++)
			cout << e[i][j] << " ";
		cout << "\n";
	}
	return 0;
}

注意要加上判斷:\([e_{i,k}\ne \infty \space \text{and} \space e_{k,j}\ne \infty]\),否則會溢位,變成負數。

我們所說的無窮大可以用 \(2^{30}\) 來代替。

可以把 floyd 核心部分封裝成函式。

  1. 難度三顆星:P1364 醫院設定

可以自己看題解理解哦~

Code:

#include <bits/stdc++.h> 
using namespace std;
const int N = 105, INF = 1 << 30;
int n, e[N][N], ans = INF, w[N];
void FloydInit() {
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			e[i][j] = i == j ? 0 : INF;
}
void floyd() {
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				if (e[i][k] != INF && e[k][j] != INF)
					e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main() {
	cin >> n;
	FloydInit();
	for (int i = 1; i <= n; i++) {
		int u, v;
		cin >> w[i] >> u >> v;
		if (u) 
			e[i][u] = e[u][i] = 1;
		if (v) 
			e[i][v] = e[v][i] = 1;
	}
	floyd();
	for (int i = 1; i <= n; i++) {
		int sum = 0;
		for (int j = 1; j <= n; j++)
			if (e[i][j] != INF)
				sum += e[i][j] * w[j];
		ans = min(ans, sum);
	}
	cout << ans;
	return 0;
}
  1. 挑戰——難度四顆星:P1828 [USACO3.2] 香甜的黃油 Sweet Butter

與上一題相似,類似加強版。

#include <bits/stdc++.h> 
using namespace std;
const int N = 1005, INF = 1 << 30;
int n, p, m, e[N][N], cow[N], ans;
void FloydInit() {
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			e[i][j] = i == j ? 0 : INF;
}
void floyd() {
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				if (e[i][k] != INF && e[k][j] != INF)
					e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main() {
	cin >> p >> n >> m;
	FloydInit();
	for (int i = 1; i <= p; i++)
		cin >> cow[i];
	for (int i = 1; i <= m; i++) {
		int a, b, d;
		cin >> a >> b >> d;
		e[a][b] = e[b][a] = min(e[a][b], d);
	}
	floyd();
	int ans = INF;
	for (int i = 1; i <= n; i++) {
		int sum = 0;
		for (int j = 1; j <= p; j++) {
			if (e[i][cow[j]] == INF) {
				sum = INF;
				break;
			}
			sum += e[i][cow[j]];
		}
		ans = min(ans, sum);
	}
	cout << ans;
	return 0;
}

最後

floyd 演算法適合需要多次訪問任意兩點之間最短路徑的問題。你,學會了嗎?

再見!

相關文章