(Shortest Path Algorithm)
WEGO
WEGO
WEGO
Edsger Wybe Dijkstra
而這,其實就是一種 貪心 演算法!
鬆弛
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
}
int N = 1e5+5; //圖上點的數量
int dis[N]; //起點 s 到 u 的距離
int N = 1e5+5; //圖上點的數量
int dis[N]; //起點 s 到 u 的距離
fill(dis,dis+N,INF); //INF 通常會設 10^9 或 10^18
int N = 1e5+5; //圖上點的數量
int dis[N]; //起點 s 到 u 的距離
fill(dis,dis+N,INF); //INF 通常會設 10^9 或 10^18
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>> pq;
pq.push({dis[s],s});
//pair存的是 {點的距離,點}
int N = 1e5+5; //圖上點的數量
int dis[N]; //起點 s 到 u 的距離
fill(dis,dis+N,INF); //INF 通常會設 10^9 或 10^18
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>> pq;
pq.push({dis[s],s});
//pair存的是 {點的距離,點}
while(!pq.empty()){
auto [disu, u] = pq.top(); pq.pop();
if(disu > dis[u]) continue; //細節,不加會TLE
for(auto [v,w] : adj[u]){
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
pq.push({dis[v],v});
}
}
}
而且他是單源點的最短路
你有 \(n\) 個地區的飛機票價與航程,而你有一張折價券可以讓一次航程打五折,你要從國家 \(1\) 飛到國家 \(n\) ,請問最少要花多少錢?
意思是到每個點的時候,只有兩種可能
已經用過折價券 或 還沒用過折價券
我們可以將原圖開兩倍的點,每個點 \(u\) 開成 \(u,u'\)
若原邊有 \(u \rightarrow v\) 則也要加入 \(u \rightarrow v'\)
以及 \(u' \rightarrow v'\)
假設原本的航班在使用折價券前 \(u \rightarrow v\) 要花 \(w\) 的時間
使用折價券時,\(u \rightarrow v'\) 要花 \(\lfloor \dfrac w 2 \rfloor\) 的時間
而使用完之後,接下來的航程時間依然相同
而且可以找圖上有沒有負環
既然我們有 \(V\) 個點, \(E\) 條邊
那我們每次都去看一條邊從 \(u\) 走到 \(v\) 會不會比較快
會的話,就更新走到 \(v\) 的最短距離
不會的話,就算了
可以證明只需要做 \(n-1\) 次就可以找到最短距離了
如果還有點可以被更新,那麼圖上有負環
for(int i = 0;i < n-1;i++){
for(auto [u,v,w] : edges){
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
}
}
}
for(auto [u,v,w] : edges){
if(dis[v] > dis[u] + w){
//圖上有負環
}
}
(Shortest Path Faster Algorithm)
他都說他 Faster 了,一定很快吧
queue<int> q;
q.push(s);
while(!q.empty()){
int u = q.front(); q.pop();
for(auto v : adj[u]){
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push(v);
}
}
}
queue<int> q;
q.push(s);
while(!q.empty()){
int u = q.front(); q.pop();
for(auto v : adj[u]){
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push(v);
}
}
}
int inqueue[N];
queue<int> q;
q.push(s);
inqueue[s] = true;
while(!q.empty()){
int u = q.front(); q.pop();
inqueue[u] = false;
for(auto v : adj[u]){
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if(!inqueue[v]) q.push(v);
}
}
}
但 SPFA 在通常情況都比較快
而且甚至有可能比 Dijkstra 還快
假設今天有四個人
他們各自有一定量的錢
而他們每個人都會跟你說他們四個人之間
所持有的錢的關係
假設今天有四個人
他們各自有一定量的錢
而他們每個人都會跟你說他們四個人之間
所持有的錢的關係
甲: 我所持有的錢比乙多 3 塊以下
乙: 我所持有的錢比丙少 4 塊以下
丙: 我所持有的錢比丁多 5 塊以下
丁: 我所持有的錢比丙少 2 塊以下
要怎麼知道他們是否說謊
他們的關係為
他們的關係為
請問是否能夠找到一組 \((x_1,x_2,x_3,x_4)\)滿足以上條件
(All Pair Shortest Path)
那如果今天要找兩兩點之間的距離呢?
但這裡我們只提 Floyd Warshall
矩陣乘法的概念寫成數學式會長下面那樣
\(C_{ij} = \sum_{k=1}^n A_{ik} \times B_{kj}\)
想成圖論的話,會很像是去尋找 \(i\) 到 \(j\) 兩點間的路徑數量
枚舉一個中繼點 \(k\),從 \(i\) 走到 \(j\)的路徑數量
可以表達成 (\(i\) 到 \(k\) 的路徑數 \(\times\) \(k\) 到 \(j\) 的路徑數) 總和
是否也能用類似的概念來做到呢?
最短路徑用矩陣乘法的寫法會長得像這樣
\(\displaystyle d_{ij} = \min_{k=i}^n d_{ik}+d_{kj}\)
不過是改成取最小值罷了
設 \(dp[i][k][j]\) 為從 \(i\) 經過 \(k\) 走到 \(j\) 的最短距離
則我們只要去枚舉 \(k\) 這個點即可!
設 \(dp[i][j]\) 為從 \(i\) 經過所有 \(k\) 走到 \(j\) 的最短距離
則我們只要去枚舉 \(k\) 這個點即可!
不過,順序會影響 DP 的答案!
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
等等,這也太短了吧
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
短歸短,但順序很重要,一定要記得要是 \(k, i, j\) 的迴圈順序
這樣才會得到正確答案
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
短歸短,但順序很重要,一定要記得要是 \(k, i, j\) 的迴圈順序
這樣才會得到正確答案
(嗎?
在以前一直被認為如果 Floyd-Warshall 順序錯了
會 produce 錯誤的答案
不過,在一篇 2019 年的論文當中
有人發現就算順序錯了,也只要多跑幾次即可!
如果迴圈順序是 IJK,那只要跑三次就正確了!
如果迴圈順序是 IKJ,那只要跑兩次就正確了!
很神奇吧! 連順序都不影響正確答案!
跑一次 Floyd-Warshall
會跑三個做 \(V\) 次的迴圈
因此時間複雜度很明顯了!
跑完之後,如果有任何一個點使得 \(d_{ii} < 0\)
則表示有負環
(Minimum Cycle)
這裡的最小環為 \(1,2,5\),長度為 \(3\)
把每條邊先刪掉之後,以 \(u\) 當起點跑最短路,看 \(d_{uv}\) 是多少,答案會是 \(\min (d_{uv}+w)\)
應該滿合理的對吧
把每條邊先刪掉之後,以 \(u\) 當起點跑最短路,看 \(d_{uv}\) 是多少,答案會是 \(\min (d_{uv}+w)\)
應該滿合理的對吧
時間複雜度: \(O(m(n+m)\log(n))\)
把每條邊先刪掉之後,以 \(u\) 當起點跑最短路,看 \(d_{uv}\) 是多少,答案會是 \(\min (d_{uv}+w)\)
時間複雜度: \(O(E(V+E)\log(V))\)
不過,當這張圖是完全圖呢?
\(E\) 最多有可能會是 \(\dfrac{V(V-1)}{2}\)
複雜度會變成 \(O(V^4 \log V)\)
就算使用在完全圖上的 Dijkstra
複雜度也會是 \(O(V^4)\)
因此換個想法,如果我們邊找最短路邊做呢?
因此換個想法,如果我們邊找最短路邊做呢?
也就是當我們在使用 Floyd-Warshall 時,順便去更新最小環
因此換個想法,如果我們邊找最短路邊做呢?
也就是當我們在使用 Floyd-Warshall 時,順便去更新最小環
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
ans = min(ans,dis[i][j]+e[i][k]+e[k][j]); //e 是 i,j 之間的邊權
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}