關於 Bellman-Ford 與 Floyd 演算法的一點感想
阿新 • • 發佈:2019-01-31
在四種常用的最短路演算法 Dijkstra, SPFA, floyd, Bellman-Ford 中, Dijks 和 SPFA 的使用較為普遍, 對大多數人來說, 也較為熟悉. 然而, floyd 與 BF 演算法在一些特定的情況下也是非常管用的, 因此有必要在這裡作出一點總結.
Floyd的基本思路就是列舉任意兩個點i, j, 再列舉任意的第三個點k, 用d[i][k] + d[j][k] 來鬆弛d[i][j]的值. 時間複雜度為O(n ^ 3), 優點在於可以求出任意兩點之間的距離, 在稠密圖中也非常管用.
#include<iostream>
#include<vector>
using namespace std;
const int &INF=100000000;
void floyd(vector<vector<int> > &distmap,//可被更新的鄰接矩陣,更新後不能確定原有邊
vector<vector<int> > &path)//路徑上到達該點的中轉點
//福利:這個函式沒有用除INF外的任何全域性量,可以直接複製!
{
const int &NODE=distmap.size();//用鄰接矩陣的大小傳遞頂點個數,減少引數傳遞
path.assign(NODE,vector <int>(NODE,-1));//初始化路徑陣列
for(int k=1; k!=NODE; ++k)//對於每一箇中轉點
for(int i=0; i!=NODE; ++i)//列舉源點
for(int j=0; j!=NODE; ++j)//列舉終點
if(distmap[i][j]>distmap[i][k]+distmap[k][j])//不滿足三角不等式
{
distmap[i][j]=distmap[i][k]+distmap[k][j];//更新
path[i][j]=k;//記錄路徑
}
}
void print(const int &beg,const int &end,
const vector<vector<int> > &path)//傳引用,避免拷貝,不佔用記憶體空間
//也可以用棧結構先進後出的特性來代替函式遞迴
{
if(path[beg][end]>=0)
{
print(beg,path[beg][end],path);
print(path[beg][end],end,path);
}
else cout<<"->"<<end;
}
int main()
{
int n_num,e_num,beg,end;//含義見下
cout<<"(不處理負權迴路)輸入點數、邊數:";
cin>>n_num>>e_num;
vector<vector<int> > path,
distmap(n_num,vector<int>(n_num,INF));//預設初始化鄰接矩陣
for(int i=0,p,q; i!=e_num; ++i)
{
cout<<"輸入第"<<i+1<<"條邊的起點、終點、長度(100000000代表無窮大,不聯通):";
cin>>p>>q;
cin>>distmap[p][q];
}
floyd(distmap,path);
cout<<"計算完畢,可以開始查詢,請輸入出發點和終點:";
cin>>beg>>end;
cout<<"最短距離為"<<distmap[beg][end]<<",列印路徑:"<<beg;
print(beg,end,path);
}
Bellman-Ford是SPFA演算法的前身, 時間複雜度為O(VE)…非常慢, 但是可以用於判斷負權迴路. 常用於差分約束系統中. Bellman-Ford這種寫法相當暴力, 直接迴圈nodeNum次, 每次列舉每一條邊, 假如這條邊可以用於鬆弛源點到端點的距離, 則進行鬆弛. 至於判斷負環, 再列舉一遍所有邊, 假如存在邊仍能用於鬆弛, 則說明存在負權迴路.
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 0x3f3f3f3f
#define N 1010
int nodenum, edgenum, original; //點,邊,起點
typedef struct Edge //邊
{
int u, v;
int cost;
}Edge;
Edge edge[N];
int dis[N], pre[N];
bool Bellman_Ford()
{
for(int i = 1; i <= nodenum; ++i) //初始化
dis[i] = (i == original ? 0 : MAX);
for(int i = 1; i <= nodenum - 1; ++i)
for(int j = 1; j <= edgenum; ++j)
if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //鬆弛(順序一定不能反~)
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
pre[edge[j].v] = edge[j].u;
}
bool flag = 1; //判斷是否含有負權迴路
for(int i = 1; i <= edgenum; ++i)
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
{
flag = 0;
break;
}
return flag;
}
void print_path(int root) //列印最短路的路徑(反向)
{
while(root != pre[root]) //前驅
{
printf("%d-->", root);
root = pre[root];
}
if(root == pre[root])
printf("%d\n", root);
}
int main()
{
scanf("%d%d%d", &nodenum, &edgenum, &original);
pre[original] = original;
for(int i = 1; i <= edgenum; ++i)
{
scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
}
if(Bellman_Ford())
for(int i = 1; i <= nodenum; ++i) //每個點最短路
{
printf("%d\n", dis[i]);
printf("Path:");
print_path(i);
}
else
printf("have negative circle\n");
return 0;
}