Dijkstra算法只适用于边的权值非负的情况,一般复杂度为O(n*n)。
Bellman-Ford是最简单最粗暴最慢的,复杂度为O(nm),比O(n*n)更复杂(因为边m比点n多),但是适用范围比Dijistra更广,可以应用于边权值为负的情况,但是不能出现负环,要不然就永远收敛不了了。
//BellmanFord只需要存储边,直接将边放到足够大的数组里
#include<stdio.h>
#include<string.h>
#define N 100010
#define INF 0x3fffffff
typedef struct Edge
{
int begin, end, value;
}ELink;
ELink edge[N]; //存储边
int dist[N], n, m;
void BellmanFord(int start)
{
int i, j;
dist[start] = 0; //源点到自己的距离为0
for(i=1; i<=n; i++) //外层是扫n遍,n个点
{
for(j=1; j<=m+m; j++) //内层是扫2m遍,因为存储了2m条边
{
if(dist[ edge[j].end ] > dist[ edge[j].begin ] + edge[j].value) //松弛
{
dist[ edge[j].end ] = dist[ edge[j].begin ] + edge[j].value;
}
}
}
}
int main()
{
int i, a, b, c;
scanf("%d%d", &n, &m);
for(i=1; i<=n; i++) //存储
dist[i] = INF;
for(i=1; i<=m; i++)
{
scanf("%d%d%d", &a, &b, &c);
edge[i].begin = a; //存储边
edge[i].end = b;
edge[i].value = c;
edge[m+i].begin = b; //反向存储一遍,否则相当于存的是有向边!!!
edge[m+i].end = a;
edge[m+i].value = c;
}
BellmanFord(1); //不妨将点1作为源点
for(i=1; i<=n; i++)
printf("%d\n", dist[i]); //输出源点到第n个点的最短距离
return 0;
}
当然,这种无脑松弛会造成很多浪费,比如当前已经松弛完毕了,但是还没有达到n*m次,程序依旧会继续在n*m的循环里走下去,而此后并不会再发生松弛了。所以我们可以设置一个flag,一旦发现没有松弛,直接break,会大大提升效率。
void BellmanFord(int start)
{
int i, j;
dist[start] = 0; //源点到自己的距离为0
for(i=1; i<=n; i++) //外层是扫n遍,n个点
{
int change = 0;
for(j=1; j<=m+m; j++) //内层是扫2m遍,因为存储了2m条边
{
if(dist[ edge[j].end ] > dist[ edge[j].begin ] + edge[j].value) //松弛
{
dist[ edge[j].end ] = dist[ edge[j].begin ] + edge[j].value;
change = 1;
}
}
if(!change)
{
break;
}
}
}
最后再说明强调一点—— 如果是无向图,存储边的话一定要正反存两遍,否则会导致无向图存成了有向图,这样就会导致最后的求解错误。
而且,如果想更简单一点的话, 直接存成邻接矩阵也是可以的,会简单很多,不过浪费了不少存储空间。
SPFA是最好使相对来说写起来也不复杂,一般正常的情况下复杂度为O(km),也可以理解为O(m),性价比最好!力荐!!!想有更多更深的理解,可以参考如下文章http://blog.sina.com.cn/s/blog_a46817ff01015g9h.html。
不必一直不停地松弛下去,如果当前节点A被松弛了(也就是说当前节点到Source Point的距离变小了),那么A的邻接点B(有可能B通过A得到到达Source Point的最短路径)就需要重新松弛一下:如果B经由A到达Source Point为最短路径,那么B一定会被松弛,否则B不会被松弛。
所以,我们需要构建一个队列,当A被松弛之后,所有A的邻接点都要被重新检测一下,看看需不需要被松弛。于是所有A的邻接点入队。之后再从队列中取出一个节点,进行相同的操作,直到队空,证明所有能松弛的都松弛完了,结束。这样就避免了Bellman-Ford盲目不停地松弛n*m次,使得算法效率大大提高。
//由于SPFA对BellmanFord进行了优化,只更新一个点周围的点,
//所以需要知道和一个点相连的点都有哪些,因此采用邻接表的形式
#include<stdio.h>
#include<stdlib.h>
#define V 200
#define N 10010
#define INF 0x3fffffff
typedef struct Edge //邻接表的链节点构造
{
int to, value;
struct Edge *link;
}ELink;
typedef struct Ver //邻接表的顶点节点构造
{
int vertex;
ELink *link; //链节点指针
}VLink;
VLink G[V]; //顶点节点型数组,最多V个
int queue[N], inqueue[N], dist[N]; //队;判断是否在队里;各个点到源点的距离
void SPFA(int start)
{
int top = 0, rear = -1;
ELink *tmp;
dist[start] = 0; //此三句对源点进行操作
queue[++rear] = start;
inqueue[start] = 1;
while(top <= rear) //直到队空(有点儿像广度搜索)
{
for(tmp = G[queue[top]].link; tmp != NULL; tmp = tmp->link) //遍历队中顶点节点的所有链节点
{
if(dist[tmp->to] > dist[queue[top]] + tmp->value) //符合条件则松弛
{
dist[tmp->to] = dist[queue[top]] + tmp->value;
if(inqueue[tmp->to] != 1) //如果没有入队则入队,并标记
{
queue[++rear] = tmp->to;
inqueue[tmp->to] = 1;
}
}
}
inqueue[queue[top]] = 0; //出队,并更改标记
++top;
}
}
void append(int a, int b, int c)
{
ELink *p, *q;
G[a].vertex = a;
p = (ELink *)malloc(sizeof(ELink));
p->to = b;
p->value = c;
p->link = NULL;
if(G[a].link == NULL)
G[a].link = p;
else
{
q = G[a].link;
while(q->link)
q = q->link;
q->link = p;
}
}
int main()
{
int n, m, i, a, b, c;
scanf("%d%d", &n, &m);
for(i=1; i<=n; i++) //从1开始初始化,距离赋INF
dist[i] = INF;
for(i=0; i<m; i++) //构造邻接表
{
scanf("%d%d%d", &a, &b, &c);
append(a, b, c); //eg:构造1-->3的链节点
append(b, a, c); //eg:构造3-->1的链节点
}
SPFA(3); //所有点到第一个点的最短距离
for(i=1; i<=n; i++) //输出所有的最短距离
printf("%d ", dist[i]);
return 0;
}
上面的算法因为用到了邻接表,所以在邻接表的构造上还是费了不少功夫的。当然不用邻接表也是可以的,使用邻接矩阵会使算法变得更简单易写,不过多浪费了存储空间:
#include <iostream>
using namespace std;
#define N 10000 // 边的最大个数
#define V 200 // 顶点的最大个数
#define INF 0x3fffffff
int edge[N][N]; // 邻接矩阵,记录边
int dist[V], queue[V], inqueue[V]; // 到源点的距离;队列;是否入队
int pre[V]; // 记录每个点的前导点,用以输出路径
int n, m; // 顶点个数;边的个数
void init() // 初始化edge/dist/pre
{
cin >> n >> m;
for(int i = 1; i <=n; i++){
dist[i] = INF;
pre[i] = i; // 每个点的终结点为自己
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <=n; j++){
edge[i][j] = INF;
}
}
int row, col, value;
while(m--){
cin >> row >> col >> value;
edge[row][col] = edge[col][row] = value;
}
}
void SPFA(int start)
{
int top = 0, rear = -1;
dist[start] = 0; // 初始化source point
queue[++rear] = start;
inqueue[start] = 1;
while(top <= rear){
int vertex = queue[top];
for(int i = 1; i <= n; i++){
int value = edge[vertex][i];
if(value < INF && dist[i] > dist[vertex] + value){
dist[i] = dist[vertex] + value;
pre[i] = vertex;
if(!inqueue[i]){
queue[++rear] = i;
inqueue[i] = 1;
}
}
}
++top;
inqueue[vertex] = 0;
}
}
void printPath(int v) // 按照pre逆序输出路径
{
while(v != pre[v]){
cout << v << "<--";
v = pre[v];
}
cout << v << endl;
}
int main()
{
init();
cout << "Input a source point: ";
int start;
cin >> start;
SPFA(start);
for(int i = 1; i <=n; i++){
cout << "Vertex: " << i << endl
<< "Dist to Source Point: " << dist[i] << endl
<< "Path: ";
printPath(i);
}
return 0;
}
算法运行结果如下:
pArch➜ tmp ᐅ ./spfa
7 9
1 2 10
1 3 2
2 5 1
3 4 2
3 6 11
4 5 4
4 6 6
5 7 7
6 7 3
Input a source point: 1
Vertex: 1
Dist to Source Point: 0
Path: 1
Vertex: 2
Dist to Source Point: 9
Path: 2<--5<--4<--3<--1
Vertex: 3
Dist to Source Point: 2
Path: 3<--1
Vertex: 4
Dist to Source Point: 4
Path: 4<--3<--1
Vertex: 5
Dist to Source Point: 8
Path: 5<--4<--3<--1
Vertex: 6
Dist to Source Point: 10
Path: 6<--4<--3<--1
Vertex: 7
Dist to Source Point: 13
Path: 7<--6<--4<--3<--1
最后说的是Dijkstra算法,如果不优化则复杂度为O(n*n),相对来说最难写,且复杂度还不如SPFA好,所以性价比不是最好,但是跟Prim求最小生成树极其相似,两个算法可以想通。详情请参考http://blog.csdn.net/puppylpg/article/details/41440459,这里就不再贴代码了。
而且,在这些点存储的过程中,点从1到n,所以我更倾向于从数组的[1]开始存储,感觉这样的话在很多方面写起来都比较容易。