最短路徑(離散)
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
最短路徑(離散)
- 318