最短路徑(離散)

20506王大宇

我的slides: https://slides.com/yeedrag

保證比林子鈞的二分圖簡單oaob

正常人看到二分圖:

最短路徑問題?

給你一個帶權的圖,問你從點A到其他點的最短距離

d(0,3) = 5

d(1,4) = 3

d(2,3) = 3

關於最短路徑的最優子結構性質

性質: 若\(P(i,j) = \{{V_i..V_k..V_s..V_j}\}\) 為

點\(i\)到點\(j\)的最短路徑,則\(P(k,s)\)為點\(k\)到點\(s\)的最短路徑。

proof:

\(P(i,j) = \{{V_i..V_k..V_s..V_j}\}\) = \(P(i,k)+P(k,s)+P(s,j)\)

若有\(P(k,s)'<P(k,s)\),則會有

\(P(i,j)'=P(i,k)+P(k,s)'+P(s,j)<P(i,j)\)

和\(P(i,j)\)為\(i\)到\(j\)的最短路徑矛盾。

利用最優子結構性質,可以提出一個演算法,從\(V_0\)開始,最短路徑長度遞增,逐次生成最短路徑的算法,

Dijkstra's algorithm

\(dist[j]\)代表從\(V_0\)到\(V_j\)的最短距離

\(W(i,j)\)為點\(i\)和點\(j\)中間邊的邊權

How to dijkstra?

1.假設除了\(V_0\)所有點距離\(V_0\) = \(\infty\)

2. 選擇目前未被選擇的點中,離\(V_0\)最近的點\(V\)。

3.使用\(V\)點鬆弛周圍連接的點

4.重複以上直到所有點被選擇過

鬆弛定義: \(dist[j] = min(dist[j],dist[V]+W(V,j))\)

看V加上兩者連接的邊權,和目前的最短距離比如果比較

近就更新他!

怎麼知道\(dis[j]\)已經是最短距離可以拿來鬆弛其他人?

經過鬆弛後的結果一定比\(dis[j]\)大(無負邊情況下)

故只要\(j\)是所有未知的點中距離最小的,\(dis[j]\)一定已是最小的,固可拿來幫其他的點做鬆弛。

也因為這個性質,dijkstra負邊不適用!!

模擬:

4

1

2

5

8

10

2

3

6

a

b

c

d

e

f

a

b

c

d

e

f

a b c d e f
0 4 2 inf inf inf
0 3 2 10 12 inf
0 3 2 8 12 inf
0 3 2 8 10 11
0 3 2 8 10 11

dist:

用C++寫Dijkstra

int dis[MAXN];
//點距離起始點的距離
vector<pair<int,int>> graph[MAXN];
//存圖,使用的是相鄰串列的作法,和講義上存圖的方法不太一樣。可以參考我的其他slides
//pair裡面存[邊權,點索引值]
void dijkstra_algorithm(int start){
	priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
    //priority_queue為優先佇列,以上方寫法為例,pq.top()永遠是最小的值
	bool visited[n+1] = {false};//是否造訪過
	for(int i=1;i<=n;i++) dis[i] = INF;//假設所有點距離起始點距離無限
	dis[start] = 0;//起始點距離起始點是0
	pq.push({0,start});//把起始點放進優先佇列內
	while(pq.size()){//當還有點沒被造訪過
		auto [w,v] = pq.top();//拿取目前未造訪,離起始點最近的點
		pq.pop();//用過了pop掉
		if(visited[v]) continue;
		visited[v] = 1;
		for(auto [w_2,v_2]:graph[v]){
			if(w+w_2<dis[v_2]){//鬆弛
				dis[v_2] = w+w_2;
				pq.push({dis[v_2],v_2});//鬆完的點放入佇列
			}
		}
	}
}

其他演算法:

單點源:

Bellman-Ford

SPFA

Dijkstra

多點源:

Floyd-Warshall 

有關圖論基礎的簡報/講義:

以上的都是電研/資訊社/校培的東西

會看到大量程式,有興趣可以參考

最短路徑(離散)

By yeedrag

最短路徑(離散)

  • 293