一、问题简述
Dijkstra算法无法判断含负权边的图的最短路。如果遇到负权,在没有负权回路(回路的权值和为负,即便有负权的边)存在时,也可以采用Bellman – Ford算法正确求出最短路径。
Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=(V,E), 其源点为v0。
接下来分析回路问题,如果有正回路,则动态规划会因为松弛操作选出最优答案,必然是不会走重复回路的,那么n-1个节点就能找到再也无法松弛的结果,无须担心。但是如果是负回路,那么步数越多,则距离越小,所以对最后结果再进行松弛操作,也是可以找到答案的。
如果不存在这样的负回路,算法将给出从源点s到 图G的任意顶点v的最短路径d[v]。
附:松弛操作
二、算法思想
首先:初始化邻接矩阵
其次:进行不断的松弛操作(bellman函数)
另外:再把每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果(minus函数)。
最后:path函数去寻找路径
三、具体代码
#include <iostream>
#include<stack>
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<queue>
#include<climits>
#include<cstring>
using namespace std;
struct node0
{
int kdist;
int dist;
int path;
};
int i,j,k;
int bellman( int **graph,node0 *node,int v0,int n) //v0表示源顶点
{
for(int j=0;j<n;j++) //初始化 完成
{
node[j].kdist=graph[v0][j];
node[j].dist=graph[v0][j];
node[j].path=v0; //path记录最短路径上从v0到i的前一个顶点
if(graph[v0][j]==INT_MAX)
node[j].path=-1;
}
//完成表格 node[n-1][0到n-1],也即所有节点,在最多经过n-1个其它节点之后,最短路径问题
for(int k=2;k<n;k++)
{
//接下来目的是完成:任意一个节点u,在最多经过k个其它节点之后的最短路径为node[k][u]; 并且接下来把node[k-1][i]称为i离源点的现有最短距离。 i是u的相邻节点
for(int u=0;u<n;u++)
{
int temp=INT_MAX;int tempfrom=-1;
for(int i=0;i<n;i++)
{
if(node[i].kdist!=INT_MAX&&graph[i][u]!=INT_MAX&&i!=u
&&temp>node[i].kdist+graph[i][u])
//目前这个关联节点的现有最短距离不能是无穷远,
//这两个节点是相邻节点,距离不是无穷远
//可以松弛操作:(相邻节点i自身到源点的现有距离和ui之间的距离之和、要比u到源点的现有距离远)
{
temp=node[i].kdist+graph[i][u];
tempfrom=i;//存下来i当作前向节点
}
}
node[u].path=tempfrom;// 记住最后完成松弛操作的前向节点
int tempkdist=node[u].kdist;
node[u].kdist=node[u].dist;
node[u].dist=min(tempkdist,temp) ;
}
}
}
int minus0(int **graph,node0 *node,int n)
//判断是否有负回路。现在经过了n-1个点得到最短路径node[u].dist,那么如果有负回路,则再经过别的点,可以让路径更短,对最后结果进行一次松弛操作判断
{
for(int u=0;u<n;u++)
{
int temp=INT_MAX;int tempfrom=-1;
for(int i=0;i<n;i++)
{
if(node[i].dist!=INT_MAX&&graph[i][u]!=INT_MAX&&i!=u
&&temp>node[i].dist+graph[i][u])
{
temp=node[i].dist+graph[i][u];
tempfrom=i;//存下来i当作前向节点
}
}
if(temp<node[u].dist)//注意是dist。
{ return -1;}
}
return 0;
}
void showPath(node0 *node,int i,int v0,int n) //打印最短路径上的各个顶点
{
int *store=(int *)malloc(sizeof(int)*n);
int count=0;
while(i!=v0)
{
store[count++]=i;
i=node[i].path; //node[n-1][u].path里面存着的都是最后也即最好的让它完成松弛操作的前向节点
}
store[count]=v0;
for(int j=count;j>=0;j--)
cout<<store[j]<<ends;
free(store);
}
void input(int **graph,int n,int edge)
//完成邻接矩阵
{
int i,j;
int from,to,weight;
int v0;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(i!=j)
graph[i][j]=INT_MAX;
}
graph[i][i]=0;
}
cout<<"请输入from,to,weight"<<endl;
for (int i = 0; i < edge; i++)
{
fin>>from>>to>>weight;
graph[from][to]=weight;
}
}
int main()
{
int n,edge,v0; //表示输入的顶点数和边数
cout<<"请输入节点和边数"<<endl;
cin>>n>>edge;
cout<<"请输入源点"<<endl;
fin>>v0;
int **graph;
graph=new int *[n+1];//n行,n列。
for(int i=0;i<n;i++)
graph[i]=new int [n+1];
input(graph,n,edge);
node0 *node;
node=new node0[n+1];//num+1行,all+1列。
int lookme;
bellman(graph,node,v0,n);
lookme=minus0(graph,node,n);
if(lookme==-1)
cout<<"警告!警告!存在负环路!系统即将关闭"<<endl;
else for(int i=0;i<n;i++)
{
if(i!=v0)
{
if(node[i].dist!=INT_MAX)
{
cout<<i<<"的路径是";
showPath(node,i,v0,n);
cout<<endl<<" 距离为"<<node[i].dist<<endl;
}
else cout<<i<<"无法到达"<<endl;
}
}
}
四、实验结果
5 10 0
0 1 6
0 3 7
1 2 5
1 4 -4
1 3 8
3 2 -3
3 4 9
2 1 -2
4 3 7
4 0 2