背景知識
圖簡介
圖由節點和邊組成,邊有方向的圖稱爲有向圖,邊沒有方向的圖稱爲無向圖,最短路徑算法裏可以把無向圖視爲雙向連接的有向圖。
邊有權重的圖稱爲有權圖,邊沒有權重的圖稱爲無權圖,無權圖可以視爲邊的權重均爲1的圖。
點對點最短路徑
求圖中任意(所有)兩點之間的最短路徑,
Floyd-Warshall算法
概述
點對點路徑可以通過將每個點作爲源點使用Dijkstra算法或者Bellman-Ford算法來求得,算法複雜度分別爲O(V^3),O(E*V^2)。
Folyd-Warshall算法複雜度並沒有降低,是O(V^3),但是Floyd的算法非常簡潔、優雅,嗯,比前面說到的兩個簡潔了不只一點點。代碼核心就三個循環 + 一個距離更新語句。
核心思想
循環V次,每次循環使用k作爲中繼點,k是循環的迭代係數,初始爲0,代表當前循環體中使用的中繼節點)分別計算所有點對點之間以k爲中繼點的距離,如果以k爲中繼點的距離更小,就更新最短路徑。
算法步驟
dis[][]用於存儲每個點對的距離,midNode[][]用於存儲每個點對的中繼點信息
Step1:初始化,dis[][]初始化爲圖中各邊的權重,dis[i][i]初始化爲0,沒有邊直接連接的點對初始化爲INT_MAX,
Step2:遍歷每個節點:對每個節點k,執行Step3
Step3:遍歷所有點對(v,u),若dis[v][k], dis[k][u]均非INT_MAX,且dis[v][k] + dis[k][u] 小於 dis[v][u], 執行:dis[v][u] = dis[v][k] + dis[k][u];midNode[v][u] = k;
Step3:遍歷同一節點的距離,即dis[i][i],如果dis[i][i]小於0,說明圖中存在負環。
爲什麼Folyd算法能獲得最短路徑
其實最讓人懷疑的是使用中繼點更新距離時(dis[v][u] = dis[v][k] + dis[k][u])dis[v][k]或者dis[k][u]可能還沒有獲得最小值。下面就簡單說一下爲什麼dis[k][u]已經獲得最小值了。
假設已知一條最短路徑,例如(1, 3, 5, 7, 2, 4),那麼dis[1][4]最晚是在7作爲中繼點時獲得最大路徑(因爲7是1->4路徑上最後一個被遍歷到的中間節點),假設dis[1][7],dis[7][4]已經獲得了最小值,如果此時dis[1][4]如果沒有獲得最小值,那麼dis[1][4]必然被更新爲dis[1][7] + dis[7][4],dis[1][4]獲得了最短路徑,
下面只需要證明dis[1][7] 和dis[7][4]已經找到了最短路徑
以7->4爲例,因爲已知(1, 3, 5, 7, 2, 4)是最短路徑,所以(7, 2, 4)一定也是最短路徑,所以在2作爲中繼點時,dis[7][4]一定會被更新爲dis[7][2] + dis[2][4](因爲一隻2在7->4最短路徑上,在2這裏肯定要更新的)
同理因爲(7, 2, 4)是最短路徑,所以(7, 2)和(2, 4)也分別爲7->2 和2->4的最短路徑。
綜上所述:Floyd算法一定會找到(1, 3, 5, 7 ,2 ,4)這條最短路徑。
判斷負環的原理
如果存在負環,假設點v在負環上,dis[v][v] 經過這條負環獲得的距離小於0(dis[v][v]的初始值),會被更新爲負值。因此通過判斷每個節點自己到自己的距離是否有負值就可以判斷負環了。
C++實現
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int printPath()
{
vector<vector<int>> graph{{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}};
vector<vector<int>> midNode;
vector<vector<int>> distances = floydWarshall(graph, midNode);
for(int i = 0; i < graph.size(); i++)
{
for(int j = 0; j < graph.size(); j++)
{
cout << i << "->" << j << ": ";
print(midNode, i, j, midNode[i][j]);
cout << " dis:" << distances[i][j] << endl;
}
cout << endl;
}
return 0;
}
void print(vector<vector<int>> midNode, int head, int tail, int mid)
{
cout << head << " ";
if(head == tail)
{
return;
}
printUtil(midNode, head, tail, midNode[head][tail]);
cout << tail << " ";
}
void printUtil(vector<vector<int>> midNode, int head, int tail, int mid)
{
if(mid == -1)
{
return;
}
printUtil(midNode, head, mid, midNode[head][mid]);
cout << mid << " ";
printUtil(midNode, mid, tail, midNode[mid][tail]);
}
vector<vector<int>> floydWarshall(vector<vector<int>> &graph, vector<vector<int>> &midNode)
{
///initialization
midNode = vector<vector<int>>(graph.size(), vector<int>(graph.size(), -1));
vector<vector<int>> distances(graph.size(), vector<int>(graph.size(), INT_MAX));
for(int i = 0; i < graph.size(); i++)
{
for(int j = 0; j < graph.size(); j++)
{
if(graph[i][j] != 0)
{
distances[i][j] = graph[i][j];
} else if(i == j)
{
distances[i][j] = 0;
}
}
}
////find shortest path
for(int k = 0; k < graph.size(); k++) ///intermediate node
{
///traverse each node pair
for(int i = 0; i < graph.size(); i++)
{
for(int j = 0; j < graph.size(); j++)
{
if(distances[i][k] != INT_MAX && distances[k][j] != INT_MAX &&
distances[i][k] + distances[k][j] < distances[i][j])
{
distances[i][j] = distances[i][k] + distances[k][j];
midNode[i][j] = k;
}
}
}
}
///detect negative ring
for(int i = 0; i < distances.size(); i++)
{
if(distances[i][i] < 0)
{
return vector<vector<int>>();
}
}
return distances;
}
};
int main()
{
return Solution().printPath();
}