1,
Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。
这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼(Richard Bellman, 动态规划的提出者)和小莱斯特•福特(Lester Ford)发明。
适用条件&范围:
单源最短路径(从源点s到其它所有顶点v);
有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
边权可正可负(如有负权回路输出错误提示);
差分约束系统;
2,Bellman-Ford算法的流程如下:
分为三步:
(1),初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
(2),进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
(3),遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v)
若有则返回false,表示途中存在从源点可达的权为负的环路。
我认为关键是在遍历所有边的操作上,因为算法主体比较简单!
要遍历所有的边,我们在初始化图的时候,用一个边集数组保存所有的边,每个元素里包含边的起始节点和终端节点以及权值。
还有我认为处理边时不像算法导论上举的一个例子那样有顺序的处理,也能得到正确结果!
下边是代码:
#include "stdafx.h"
#include <fstream>
#include <iostream>
using namespace std;
#define path "D:\\test.txt"
#define INFFINITY 65535
struct edge{
int fromvex;
int endvex;
int weight;
};
struct Graph{
int vexnum;
int arcnum;
struct edge *edgeset;//边集数组
int *pre;
int *dist;
};
void Readfile(Graph &g)
{
ifstream infile(path);
infile >> g.vexnum >> g.arcnum;//读入顶点数和边数
g.edgeset = new edge[g.vexnum];
g.pre = new int[g.vexnum];//各顶点最短路径上节点的前驱节点
g.dist = new int[g.vexnum];//各顶点到源节点的距离数组
for (int i = 0; i < g.vexnum; i++)
{
g.pre[i] = i;
g.dist[i] = INFFINITY;
}
for (int i = 0; i < g.arcnum; i++)
{
infile >> g.edgeset[i].fromvex >> g.edgeset[i].endvex >> g.edgeset[i].weight;
}
}
void Relax(Graph &g, int u, int v, int weight)
{
if (g.dist[u] == INFFINITY)
{
return;
}
if (g.dist[v] > g.dist[u] + weight)
{
g.dist[v] = g.dist[u] + weight;
g.pre[v] = u;
}
}
bool Bellmanford(Graph &g, int source)
{
g.dist[source] = 0;
for (int i = 1; i < g.vexnum; i++)
{
for (int j = 0; j < g.arcnum; j++)
{
Relax(g, g.edgeset[j].fromvex, g.edgeset[j].endvex, g.edgeset[j].weight);
}
}
int flag = 1;
for (int k = 0; k < g.arcnum; k++)
{
if (g.dist[g.edgeset[k].fromvex]!= INFFINITY && g.dist[g.edgeset[k].endvex] > g.dist[g.edgeset[k].fromvex] + g.edgeset[k].weight)
{
flag = 0;
break;
}
}
if (flag)
{
return true;
}
else
return false;
}
void printroot(Graph g, int node)//打印最短路径(反向)
{
if (g.dist[node] == INFFINITY)
{
cout << "源节点不可达!" << endl;
return;
}
else
{
while (g.pre[node] != node)
{
cout << node << "-->";
node = g.pre[node];
}
cout << node;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
Graph g;
Readfile(g);
int source = 0;
cout << "请输入源节点编号,大小为0到" << g.vexnum - 1 << endl;
cin >> source;
if(Bellmanford(g, source))
{
cout << "没有源节点可到达的负权环!" << endl;
cout << "请输入要打印路径的终端节点,源节点为" << source << endl;
int end;
cin >> end;
printroot(g, end);
cout << endl;
cout << "最短距离为:" << g.dist[end];
}
else
{
cout << "存在源节点可到达的负权环!" << endl;
}
system("pause");
return 0;
}
测试文件为text.txt
8 13
0 1 6
0 3 7
1 2 5
1 3 8
1 4 -4
2 1 -2
3 2 -3
3 4 9
4 0 2
4 2 7
5 6 2
6 7 3
7 5 -8
结果为:
考虑:为什么要循环V-1次?
答:因为最短路径肯定是个简单路径,不可能包含回路的,
如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径
如果回路的权值是负的,那么肯定没有解了
图有n个点,又不能有回路
所以最短路径最多n-1边
又因为每次循环,至少relax一边
所以最多n-1次就行了
源节点到一个点的最短路径上的点仍是源节点到该点的最短路径!