最短距離

給定一個圖有n個節點m條邊和每條邊的邊權

詢問i~j的最短距離為多少

1

2

3

4

無向圖

邊沒有通行方向限制

1

2

3

2

3

6

無加權圖

加權圖

2

1

3

4

5

6

{1, 2, 3}為一連通圖塊

{4, 5, 6}為一連通圖塊

有向圖

每條邊有固定通行方向

2

5

1

6

4

3

1

2

3

5

4

2

1

-7

1

2

無加權圖

加權圖

混和圖

有向圖和無向圖結合

1

2

3

5

4

6

7

8

9

10

11

12

1.鄰接矩陣

開一個二維陣列v[ n ][ n ]

v[ i ][ j ]代表i~j邊的長度

空間複雜度為O(n²)

圖的存取

1

3

2

4

二維陣列v[ 4 ][ 4 ]

v[ 1 ][ 2 ] = 4

v[ 2 ][ 3 ] = 2

v[ 1 ][ 4 ] = 8

4

2

8

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int v[10005][10005];
int main(){
	int n, m, st, en, l;
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++){
		scanf("%d%d%d", &st, &en, &l);
		v[st][en] = l;
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			printf("%d ", v[i][j]);
		}
		printf("\n");
	}
}

開一個存放pair的vector v[n]

在pair存放連接到的點j和~j的距離

空間複雜度為O(n + m) = O(n)

圖的存取

2.鄰接串列

vector v[ 4 ]

v[ 1 ] = {{2, 4}, {4, 8}}

v[ 2 ] = {{3, 2}}

1

3

2

4

4

2

8

程式碼

#include <bits/stdc++.h>
using namespace std;
vector <pair <int, int> > v[10005];
int main(){
	int n, m;
	cin >> n >> m;
	for(int i = 0; i < m; i++){
		int a, b, w;
		cin >> a >> b >> w;
		v[a].push_back({b, w});
	}
	for(int i = 1; i <= n; i++){
		cout << i << " : ";
		for(auto j : v[i]){
			cout << j.first << ", " << j.second << " ";
		}
		cout << '\n';
	}
}

假設點i、j和起點的距離是dis[ i ]、dis[ j ]

如果dis[ i ]+i~j的距離<dis[ j ]

那麼就可以直接把dis[ j ]設為dis[ i ]+i~j

鬆弛

1

2

3

4

2

10

Floyd-Warshall

(弗洛伊德演算法)

用途

  • 計算任意兩點間的最小距離
  • 在n較小的時候使用

作法

  1. 枚舉3個點a、b、c
  2. 把b看成a、c點的中點
  3. 鬆弛a、c

1

2

3

4

5

6

3

5

4

2

1

2

4

                            時間複雜度為O(n^3)

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1e9
int dis[10005][10005];
int main(){
	int n, m, st, en, l;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(i == j)
				dis[i][j] = 0;
			else
				dis[i][j] = maxn;
		}
	}
	for(int i = 0; i < m; i++){
		scanf("%d%d%d", &st, &en, &l);
		dis[st][en] = l;
		dis[en][st] = l;
	}
	for(int k = 1; k <= n; k++){
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= n; j++){
				if(dis[i][k] + dis[k][j] < dis[i][j])
					dis[i][j] = dis[i][k] + dis[k][j];
			}
		}
	}
	scanf("%d%d", &st, &en);
	printf("%d", dis[st][en]);
}

無向加權圖中求任意兩點最小距離

Dijsktra

(戴克斯特拉演算法)

不停找到目前離起點最近的點

用途

  • 計算某一點到另一點的最短距離
  • 有向圖和無向圖皆可用

作法

  1. 尋找距離起點最近且沒走到過的可連通點
  2. 登記點為已走到過
  3. 添加可連通點並對點鬆弛

1

2

3

4

5

6

3

5

4

2

1

2

4

            時間複雜度為O(nlog(n))

特性

無法處理邊權為負數

1

2

3

5

4

-3

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1e9
vector <pair <int, int> > v[100005];
priority_queue <pair <int, int> > q;
int dis[100005];
bool u[100005] = {false};
void dijkstra(int n, int st){
	for(int i = 1; i <= n; i++){
		dis[i] = maxn;
	}
	dis[st] = 0;
	q.push({0, st});
	while(!q.empty()){
		int lowind = q.top().second;
		q.pop();
		if(u[lowind])
			continue;
		u[lowind] = true;
		for(auto i : v[lowind]){
			int newdis = dis[lowind] + i.second;
			if(newdis < dis[i.first]){
				dis[i.first] = newdis;
				q.push({-dis[i.first], i.first});
			}
		}
	}
}
int main(){
	int n, m, st, en, l;
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++){
		scanf("%d%d%d", &st, &en, &l);
		v[st].push_back({en, l});
		v[en].push_back({st, l});
	}
	scanf("%d%d", &st, &en);
	dijkstra(n, st);
	printf("%d", dis[en]);
}

無向加權圖中求兩點最小距離

例題

額外補充

Bellman-Ford

(貝爾曼-福特演算法)

  • 能處理邊權為負數的圖
  • 能判斷圖中有沒有負環

BFS

(廣度優先搜尋)

像倒水一樣,從起點散開

用途

  • 計算某一點可走到的點
  • 在無加權圖中找到兩點最短距離

作法

  1. 尋找當前可連通點
  2. 登記點為已走到過
  3. 更新距離並添加可連通點

                         時間複雜度為O(m)

1

2

3

4

5

7

6

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int dis[100005];
vector <int> v[100005];
bool u[100005] = {false};
queue <int> q;
void bfs(int st){
	q.push(st);
	u[st] = true;
	dis[st] = 0;
	while(!q.empty()){
		int tmp = q.front();
		q.pop();
		for(auto i : v[tmp]){
			if(!u[i]){
				dis[i] = dis[tmp] + 1;
				u[i] = true;
				q.push(i);
			}
		}
	}
}
int main(){
	int n, m, st, en;
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++){
		scanf("%d%d", &st, &en);
		v[st].push_back(en);
		v[en].push_back(st);
	}
	scanf("%d%d", &st, &en);
	bfs(st);
	printf("%d", dis[en]);
}

無向無加權圖中求兩點最小距離

例題

DFS

(深度優先搜尋)

用遞迴把每條路徑都走一次

用途

  • 計算某一點可走到的點
  • 計算連通區塊
  • 圖論常用到

作法

  1. 找到當前可連通點
  2. 再從可連通點找下一個點
  3. 直到找不到再return

                         時間複雜度為O(m)

1

2

4

5

6

7

8

3

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector <int> v[100005];
bool u[100005] = {false};
void dfs(int n){
	printf("%d ", n);
	u[n] = true;
	for(auto i : v[n]){
		if(!u[i]){
			dfs(i);
			printf("\n");
		}
	}
	return;
}
int main() {
	int n, m, st, en;
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++){
		scanf("%d%d", &st, &en);
		v[st].push_back(en);
		v[en].push_back(st);
	}
    for(int i = 1; i <= n; i++){
    	if(!u[i])
    		dfs(i);
    }
}

無向無加權圖中走過每個點

例題

最短距離

By patrickh

最短距離

  • 129