Dijkstra算法比较快速,但是如果遇到负边就无能为力了,而Bellman算法可以解决负边问题,只要不是负环。
这个算法数据结构没有讲过,他主要是每次对所以边进行松弛操作,进行n-1次得到最短路径。
其原理如下所述:
首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|n-1条边。
其次,从源点s可达的所有顶点如果存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。
在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。因为最短路径最多只包含|v|-1 条边,所以,只需要循环|v|-1 次。
如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 d[v]仍保持 +∞,则表明从s到v不可达。
如果有负权回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。
根据这个原理写出代码如下:
bool Bellman(int s,int n){ fill(dis,dis+n,inf); dis[s]=0; for(int i=0;i<n-1;i++){//n-1此松弛操作 int flag=1; for(int u=0;u<n;u++) { for(int j=0;j<Adj[u].size();j++) { int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]) {dis[x]=dis[u]+Adj[u][j].weight;//松弛操作 flag=0; } } } if(flag)return true;//如果这一轮没有被松弛,直接退出 } for(int u=0;u<n;u++) //判断是否存在负环 { for(int j=0;j<Adj[u].size();j++) { int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]) return false;//还能松弛存在负环,失败 } } return true; }
虽然理解简单但是这个算法耗时较大,每一轮都要遍历所有边,其实由于一个顶点u的d[u]改变时,以此点出发的邻接点d[v]才可能改变,结合Bellman树的形成过程可以类比层次遍历,用一个队列存放结点,每拿出一个结点对邻接点松弛,如果能松弛且不在队列里就让其入队,直到队空(说明无负环,)或者某个结点入队超过n-1次,说明有负环。这就是SPFA算法,一般比Dijkstra算法还要快,比较高效。代码如下:
bool SPFA(int s,int n){ fill(inq,inq+n,false);//记录是否入队 fill(num,num+n,0);//记录入队次数 fill(dis,dis+n,inf); queue<int>q; q.push(s); inq[s]=true; num[s]++; dis[s]=0; while(!q.empty()){ int u=q.front();q.pop(); inq[u]=false; for(int j=0;j<Adj[u].size();j++){ int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]){ dis[x]=dis[u]+Adj[u][j].weight; if(!inq[x]) { q.push(x); num[x]++; inq[x]=true; if(num[x]>n-1)//负环 return false; } } } } return true; }
此外关于最短路径还有一个Floyd算法,这里打包带走,下面是三个算法的全体代码:
#include<iostream> #include<vector> #include<queue> #include<algorithm> using namespace std; const int maxv=101; const int inf=10000000; struct node{ int v,weight; }; vector<node>Adj[maxv] ; int dis[101]; //bellman算法 bool Bellman(int s,int n){ fill(dis,dis+n,inf); dis[s]=0; for(int i=0;i<n-1;i++){//n-1此松弛操作 int flag=1; for(int u=0;u<n;u++) { for(int j=0;j<Adj[u].size();j++) { int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]) {dis[x]=dis[u]+Adj[u][j].weight;//松弛操作 flag=0; } } } if(flag)return true;//如果这一轮没有被松弛,直接退出 } for(int u=0;u<n;u++) //判断是否存在负环 { for(int j=0;j<Adj[u].size();j++) { int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]) return false;//还能松弛存在负环,失败 } } return true; } //SPFA算法 bool inq[maxv] ; int num[maxv]; bool SPFA(int s,int n){ fill(inq,inq+n,false); fill(num,num+n,0); fill(dis,dis+n,inf); queue<int>q; q.push(s); inq[s]=true; num[s]++; dis[s]=0; while(!q.empty()){ int u=q.front();q.pop(); inq[u]=false; for(int j=0;j<Adj[u].size();j++){ int x=Adj[u][j].v; if(dis[u]+Adj[u][j].weight<dis[x]){ dis[x]=dis[u]+Adj[u][j].weight; if(!inq[x]) { q.push(x); num[x]++; inq[x]=true; if(num[x]>n-1) return false; } } } } return true; } //floyd算法 int d[maxv][maxv] ; void floyd(int n){ for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(d[i][k]!=inf&&d[k][j]!=inf&&d[i][k]+d[k][j]<d[i][j]) d[i][j]=d[i][k]+d[k][j]; } int main(){ int n,m,s,t; int x,y,w; node temp; scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=0;i<m;i++){ scanf("%d%d%d",&x,&y,&w); temp.weight=w; temp.v=x; Adj[y].push_back(temp); temp.v=y; Adj[x].push_back(temp); } if(SPFA(s,n)) { for(int i=0;i<n;i++) printf("%d ",dis[i]); } return 0; }