最短路徑---Floyd演算法(C++)
Floyd演算法的介紹
演算法的特點:
弗洛伊德演算法是解決任意兩點間的最短路徑的一種演算法,可以正確處理有向圖或有向圖或負權(但不可存在負權迴路)的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。
演算法的思路
通過Floyd計算圖G=(V,E)中各個頂點的最短路徑時,需要引入兩個矩陣,矩陣S中的元素a[i][j]表示頂點i(第i個頂點)到頂點j(第j個頂點)的距離。矩陣P中的元素b[i][j],表示頂點i到頂點j經過了b[i][j]記錄的值所表示的頂點。
假設圖G中頂點個數為N,則需要對矩陣D和矩陣P進行N次更新。初始時,矩陣D中頂點a[i][j]的距離為頂點i到頂點j的權值;如果i和j不相鄰,則a[i][j]=∞,矩陣P的值為頂點b[i][j]的j的值。 接下來開始,對矩陣D進行N次更新。第1次更新時,如果”a[i][j]的距離” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i與j之間經過第1個頂點的距離”),則更新a[i][j]為”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新時,如果”a[i][j]的距離” > “a[i][k-1]+a[k-1][j]”,則更新a[i][j]為”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之後,操作完成!
Floyd演算法的例項過程
上面,我們已經介紹了演算法的思路,如果,你覺得還是不理解,那麼通過一個實際的例子,把演算法的過程過一遍,你就明白了,如下圖,我們求下圖的每個點對之間的最短路徑的過程如下:
第一步:
我們先初始化兩個矩陣,得到下圖兩個矩陣:
、
第二步:
以v1為中階,更新兩個矩陣:發現,a[1][0]+a[0][6] < a[1][6] 和a[6][0]+a[0][1] < a[6][1],所以我們只需要矩陣D和矩陣P,結果如下:
通過矩陣P,我發現v2–v7的最短路徑是:v2–v1–v7
第三步:
以v2作為中介,來更新我們的兩個矩陣,使用同樣的原理,掃描整個矩陣,得到如下圖的結果:
OK,到這裡我們也就應該明白Floyd演算法是如何工作的了,他每次都會選擇一箇中介點,然後,遍歷整個矩陣,查詢需要更新的值,下面還剩下五步,就不繼續演示下去了,理解了方法,我們就可以寫程式碼了。
程式碼:
floyd.h
#ifndef FLOYD_H #define FLOYD_H #pragma once #include <iostream> #include <string> using namespace std; class Graph_DG { private: int vexnum; // 圖中頂點個數 int edge; // 圖的邊數 int **arc; // 鄰接矩陣 int **dis; // 記錄各個頂點最短路徑的資訊 int **path; // 記錄各個最短路徑 public: Graph_DG(int v, int e); ~Graph_DG(); // 判斷每次輸入的邊是否合法,頂點從1開始編號 bool check_edge_value(int start, int end); void creatGraph(int kind); void print(); // 列印鄰接矩陣 void Floyd(); void print_path(); // 列印最短路徑 }; #endif // FLOYD_H
floyd.cpp
#include "floyd.h"
Graph_DG::Graph_DG(int v, int e)
{
vexnum = v;
edge = e;
arc = new int*[vexnum];
dis = new int*[vexnum];
path = new int*[vexnum];
for (int i = 0; i < vexnum; ++i)
{
arc[i] = new int[vexnum];
dis[i] = new int[vexnum];
path[i] = new int[vexnum];
for (int j = 0; j < vexnum; ++j)
{
// 鄰接矩陣初始化為無窮大
arc[i][j] = INT_MAX;
}
}
}
Graph_DG::~Graph_DG()
{
for (int i = 0; i < vexnum; ++i)
{
delete [] this->arc[i];
delete [] this->dis[i];
delete [] this->path[i];
}
delete [] arc;
delete [] dis;
delete [] path;
}
// 判斷我們每次輸入的的邊的資訊是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end)
{
if (start < 1 || end < 1 || start > vexnum || end > vexnum) // Floyd演算法,權值可以為負
{
return false;
}
return true;
}
void Graph_DG::creatGraph(int kind)
{
cout << "請輸入每條邊的起點和終點(頂點編號從1開始)以及其權重" << endl;
int start, end, weight;
int count = 0;
while (count != edge)
{
cin >> start >> end >> weight;
while (!check_edge_value(start, end))
{
cout << "輸入的邊的資訊不合法,請重新輸入" << endl;
cin >> start >> end >> weight;
}
arc[start-1][end-1] = weight;
// 無向圖新增這一句
if (kind == 2)
{
arc[end-1][start-1] = weight;
}
++count;
}
}
void Graph_DG::print()
{
cout << "圖的鄰接矩陣為:" << endl;
int row = 0;
int col = 0;
while (row != vexnum)
{
col = 0;
while (col != vexnum)
{
if (arc[row][col] == INT_MAX)
{
cout << "∞ ";
}
else
{
cout << arc[row][col] << " ";
}
++col;
}
cout << endl;
++row;
}
}
void Graph_DG::Floyd()
{
int row, col;
for (row = 0; row < vexnum; ++row)
{
for (col = 0; col < vexnum; ++col)
{
// 把矩陣D初始化為鄰接矩陣
dis[row][col] = arc[row][col];
// 矩陣P的初值為各個邊的終點頂點下標
path[row][col] = col;
}
}
// 三重迴圈,用於計算每兩個點之間的最短路徑.【動態規劃的思想】
int temp, select;
for (temp = 0; temp < vexnum; ++temp)
{
for (row = 0; row < vexnum; ++row)
{
for (col = 0; col < vexnum; ++ col)
{
// 為防止溢位,引入一個select值
select = (dis[row][temp] == INT_MAX || dis[temp][col] == INT_MAX) ?
INT_MAX : dis[row][temp] + dis[temp][col];
if (dis[row][col] > select)
{
// 更新D矩陣
dis[row][col] = select;
// 更新P矩陣
path[row][col] = path[row][temp];
}
}
}
}
}
void Graph_DG::print_path()
{
cout << "各個頂點對的最短路徑:" << endl;
int row, col, temp;
for (row = 0; row < vexnum; ++row)
{
for (col = row + 1; col < vexnum; ++col)
{
cout << "v" << to_string(row + 1) << "---v" << to_string(col + 1) << " weight: "
<< dis[row][col] << " path: v" << to_string(row + 1);
temp = path[row][col];
// 迴圈輸出途徑的每條路徑
while (temp != col)
{
// path[i][j] = k, 表示從i走到j,第一步需要從i到k
// 同理,再從k到j,第一步需要走到path[k][j]
cout << "-->v" << to_string(temp + 1);
temp = path[temp][col];
}
cout << "-->v" << to_string(col + 1) << endl;
}
cout << endl;
}
}
main.cpp
#include <floyd.h>
//頂點數和邊數的關係是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int vexnum, int edge)
{
if (vexnum <= 0 || edge <= 0 || (vexnum*(vexnum-1)/2) < edge)
{
return false;
}
return true;
}
int main()
{
int vexnum, edge, kind;
cout << "輸入圖的種類:1代表有向圖,2代表無向圖" << endl;
cin >> kind;
while (1)
{
if (kind == 1 || kind == 2)
{
break;
}
else
{
cout << "輸入的圖的種類編號不合法,請重新輸入:1代表有向圖,2代表無向圖" << endl;
cin >> kind;
}
}
cout << "輸入圖的頂點個數和邊的條數:" << endl;
cin >> vexnum >> edge;
while(!check(vexnum, edge))
{
cout << "輸入的數值不合法,請重新輸入" << endl;
cin >> vexnum >> edge;
}
Graph_DG graph(vexnum, edge);
graph.creatGraph(kind);
graph.print();
graph.Floyd();
graph.print_path();
return 0;
}
執行結果:
注:
- 原文程式碼中,解構函式部分delete後面沒有加 [] ,會出現記憶體洩漏的情況。
- Floyd演算法可以對負權值圖進行求解,若出現負環,則原問題本身無法求解,因此Floyd演算法也可用於檢驗是否出現負環(初始化所有dis[i][i] = 0,在第三層迴圈relax結束後,加一個判斷dis[i][i]是否為負,若為負則說明存在負環,詳細程式碼可參考下一篇部落格)。
- 在列印路徑部分,對path[i][j]的理解如下:path[i][j] = k, 表示從i走到j,第一步需要從i到k;同理,再從k到j,第一步需要走到path[k][j]。
- Floyd演算法本身採用的是動態規劃的思想。
參考資料:https://blog.csdn.net/qq_35644234/article/details/60875818