n^2
遍深度或廣度優先搜索,即對每兩個點都進行一次深度或廣度優先搜索,便可以求得任意兩點之間的最短路徑。e[i][1]+e[1][j]
是否比e[i][j]
要小即可。其中,e[i][j]
表示從i到j頂點之間的路程,e[i][1]+e[1][j]
表示的是從i先到1,再從1到j的路程之和。for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j)
if(e[i][j] > e[i][1] + e[1][j])
e[i][j] = e[i][1] + e[1][j];
e[i][2]+e[2][j]
是否比e[i][j]
更短?//經過1
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j)
if(e[i][j] > e[i][1] + e[1][j])
e[i][j] = e[i][1] + e[1][j];
//經過2
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j)
if(e[i][j] > e[i][2] + e[2][j])
e[i][j] = e[i][2] + e[2][j];
for(k = 1; k <= n; ++k)
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j)
if(e[i][j] > e[i][k] + e[k][j])
e[i][j] = e[i][k] + e[k][j];
#include
#include
#include
#include
using namespace std;
int main(void)
{
int v = 4; //頂點數
int e = 8; //邊數
vector> graph(v, vector(v, INT_MAX));
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = 2;
graph[0][2] = 6;
graph[0][3] = 4;
graph[1][2] = 3;
graph[2][0] = 7;
graph[2][3] = 1;
graph[3][0] = 5;
graph[3][2] = 12;
/*
0 2 6 4
∞ 0 3 ∞
7 ∞ 0 1
5 ∞ 12 0
*/
//1~k作為中轉點
for (int k = 0; k < v; ++k)
{
for (int i = 0; i < v; ++i)
{
for (int j = 0; j < v; ++j)
{
if (graph[i][k] < INT_MAX && graph[k][j] < INT_MAX
&& graph[i][j] > graph[i][k] + graph[k][j]) //防止相加溢出
graph[i][j] = graph[i][k] + graph[k][j];
}
}
}
for (const auto & line : graph)
{
for (const auto & point : line)
cout << setw(3) << point;
cout << endl;
}
return 0;
}
O(N^3)
。由於Floyd-Warshall算法實現起來非常容易,所以如果時間復雜度要求不高,使用Floyd-Warshall來指定兩點之間的最短路徑或者指定一個點到其余各個頂點的最短路徑也是可行的。如果一個圖中帶有“負權回路”,那麼這個圖則沒有最短路徑。
#include
#include
#include
using namespace std;
int main(void)
{
int v = 6; //頂點數
int e = 9; //邊數
vector> graph(v, vector(v, INT_MAX));
vector flags(v, false);
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = 1;
graph[0][2] = 12;
graph[1][2] = 9;
graph[1][3] = 3;
graph[2][4] = 5;
graph[3][2] = 4;
graph[3][4] = 13;
graph[3][5] = 15;
graph[4][5] = 4;
/*
0 2 6 4
∞ 0 3 ∞
7 ∞ 0 1
5 ∞ 12 0
*/
vector dis(v);
//以0為源點,初始化
for (int i = 0; i < v; ++i)
dis[i] = graph[0][i];
flags[0] = true;
//Dijkstra
for (int i = 1; i < v; ++i) //只需v-1次循環
{
//找到目前離源點最近的未標記的點
int min_dis = INT_MAX; //記錄最小
int v_index; //最近是哪個點
for (int j = 0; j < v; ++j)
{
if (flags[j] == false && dis[j] < min_dis)
{
min_dis = dis[j];
v_index = j;
}
}
flags[v_index] = true; //標記這個最近的點為確定
//以找到的點所關聯的邊來進行松弛操作
for (int j = 0; j < graph[v_index].size(); ++j)
{
if (graph[v_index][j] < INT_MAX)
{
//這條邊是存在的
if (dis[j] > dis[v_index] + graph[v_index][j])
dis[j] = dis[v_index] + graph[v_index][j];
}
}
}
//單源最短路徑結果
for (const auto & i : dis)
cout << i << ' ';
cout << endl;
return 0;
}
O(N^2)
,每次找到離源點最近的點的時間復雜度為O(N)
,可以用堆進行優化,使得O(N)
降低為O(logN)
。O((M+N)logN)
。在最壞情況下M就是N^2,這樣(M+N)logN比N^2還大,但是大多數情況下並不會有那麼多邊,所以通常(M+N)logN要比N^2小很多。
//核心代碼
for(k = 1; k <= n - 1; ++k)
for(i = 1; i <= m; ++i)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
那需要進行多少輪?
n-1?最短路徑中不可能包含回路嗎?
#include
#include
#include
using namespace std;
struct Edge
{
int from; //邊起點
int to; //邊終點
int weight; //邊權重
};
int main(void)
{
int v = 5; //頂點數
int e = 5; //邊數
const int INT_INF = INT_MAX - 10000; //防止溢出的最大值
vector dis(v);
//以0為源點,初始化
for (int i = 0; i < v; ++i)
dis[i] = INT_INF;
dis[0] = 0;
vector edges = {
{1, 2, 2},
{0, 1, -3},
{0, 4, 5},
{3, 4, 2},
{2, 3, 3}
};
//Bellman-Ford
for (int turn = 0; turn < v - 1; ++turn)
{
vector detect = dis; //備份
for (const auto & edge : edges)
{
if (dis[edge.to] > dis[edge.from] + edge.weight)
dis[edge.to] = dis[edge.from] + edge.weight;
}
if (detect == dis) //如果dis數組沒有更新,提前退出循環結束算法
break;
}
for (const auto & point : dis)
cout << point << ' ';
cout << endl;
//檢測負權回路
bool isloop = false;
for (const auto & edge : edges)
if (dis[edge.to] > dis[edge.from] + edge.weight)
isloop = true;
cout << (isloop ? "存在" : "不存在") << "回路" << endl;
return 0;
}
O(NM)
(比Dijkstra算法還高?),可以繼續優化。在實際操作中,Bellman-Ford算法經常會在未達到n-1輪松弛前就已經計算出最短路徑(n-1只是最大值)。因此可以添加一個數組來備份dis,如果在新一輪的松弛中數組dis沒有發生變化,則可以提前跳出循環。(優化的前提基於整個數組沒有變化)#include
#include
#include
#include
#include
using namespace std;
struct Edge
{
int from; //邊起點
int to; //邊終點
int weight; //邊權重
};
int main(void)
{
int v = 5; //頂點數
int e = 7; //邊數
const int INT_INF = INT_MAX - 10000; //防止溢出的最大值
vector dis(v);
queue vex_opt; //bellman-ford優化隊列
vector flags(v, false); //標記點是否在隊列中
vector> graph(v); //list和forward_list也行
graph[0].push_back({ 0, 1, 2 });
graph[0].push_back({ 0, 4, 10 });
graph[1].push_back({ 1, 2, 3 });
graph[1].push_back({ 1, 4, 7 });
graph[2].push_back({ 2, 3, 4 });
graph[3].push_back({ 3, 4, 5 });
graph[4].push_back({ 4, 2, 6 });
//以0為源點,初始化
for (int i = 0; i < v; ++i)
dis[i] = INT_INF;
dis[0] = 0;
flags[0] = true;
vex_opt.push(0);
//判斷是否存在負權回路
vector loop(v, 0);
++loop[0];
while (!vex_opt.empty() && find(loop.begin(), loop.end(), v) == loop.end())
//隊列不為空和沒檢測出負權回路的時候循環
{
//掃描當前頂點的所有邊
for (const auto & edge : graph[vex_opt.front()])
{
//判斷是否松弛成功
if (dis[edge.to] > dis[edge.from] + edge.weight)
{
dis[edge.to] = dis[edge.from] + edge.weight;
//判斷頂點是否在隊列中
if (flags[edge.to] == false)
{
//入隊
flags[edge.to] = true;
vex_opt.push(edge.to);
++loop[edge.to];
}
}
}
//出隊
flags[vex_opt.front()] = false;
vex_opt.pop();
}
if (vex_opt.empty())
{
for (const auto & point : dis)
cout << point << ' ';
cout << endl;
cout << "不存在負權回路" << endl;
}
else
{
cout << "存在負權回路" << endl;
}
return 0;
}
O(MN)
。通過隊列優化的Bellman-Ford算法如何判斷一個圖是否有負環呢?
O(N^2)
O(M)
O(M)
時間復雜度
O(N^3)
O((M+N)logN)
O(MN)
適用情況
稠密圖,和頂點關系密切
稠密圖,和頂點關系密切
稀疏圖,和邊關系密切
負權
可以解決負權
不能解決負權
可以解決負權