一、Dijkstra算法
Dijkstra算法是解决带权重的有向图最短路径问题,要求所有边权重为非负值。
以下是算法导论上给出的伪码,采用了是贪心策略实现的,总是寻找集合V-S(S集合是加入)中最近的节点加入到S集合中,算法时间复杂度依赖于最小优先队列的实现方式。
Dijkstra(G,w,s) for each vertex v in G.V v.d=FIN; //最短路径估计 v.π=NIL; //前驱节点 s.d=0; //到源节点距离为0 S=NULL; Q=G.V; while !Q.empty u=EXTRACT-MIN(Q) //最小优先队列 S=SU{u} for each vertex v in G.Adj[u] RELAX(u,v,w) RELAX(u,v,w) //松弛操作 if v.d>u.d+w(u,v) v.d=u.d+w(u,v) v.π=u
下面是C++的实现,时间复杂度是O(N^2),N为节点数。
1 /***prev数组保存已加入集合节点的前驱,dist数组保存每个节点到源节点的距离,vex边数,v源节点***/ 2 const int INF=0xffff; 3 const int MAX=100; 4 void Dijkstra(vector<vector<int>>G,int *prev,int *dist,int vex,int v) 5 { 6 bool s[MAX]; //记录节点是否加入集合 7 for(int i=0;i<vex;i++) 8 { 9 dist[i]=G[v][i]; //源节点到每个节点的距离 10 s[i]=0; //初始化未使用节点 11 if(dist[i]=INF) 12 prev[i]=0; //设置前驱节点 13 else 14 prev[i]=v; 15 } 16 dist[v]=0; 17 s[v]=1; //将源节点加入集合 18 19 for(int i=1;i<vex;i++) 20 { 21 int temp=MAX; 22 int u=v; 23 for(int j=0;j<vex;j++) //找出dist中最小值 24 { 25 if((!s[j])&&dist[j]<temp) 26 { 27 u=j; 28 temp=dist[j]; 29 } 30 } 31 s[u]=1; //加入集合 32 33 for(int j=0;j<=vex;j++) //松弛操作 34 { 35 if((!s[j])&&G[u][j]<MAX) 36 { 37 if(dist[u]+G[u][j]<dist[j]) 38 { 39 dist[j]=dist[u]+G[u][j]; 40 prev[j]=u; 41 } 42 } 43 } 44 } 45 }
取终点将前驱数组逆序就可以得到最短路径的路线图了。
二、Bellman-Ford算法
该算法解决的是一般的单源最短路径问题,可以允许边的权重为负值,算法返回一个布尔值,表明是否存在一个从源节点可以到达的权重为负值的环路。
算法导论给出的伪码,时间复杂度为O(V*E)
Bellman-Ford(G,w,s) for each vertex v in G.V v.d=FIN; //最短路径估计 v.π=NIL; //前驱节点 s.d=0; //到源节点距离为0 for i=1 to |G.V|-1 for each edge(u,v) in G.E RELAX(u,v,w) //与dijstra算法一样的松弛操作 for each edge(u,v) in G.E //判断是否有存在权重为负值的环路 if(v.d>v.d+w(u.v)) return FALSE return TRUE
理解Bellman-Ford算法,首先我们要理解松弛操作,下面给出算法导论给出的路径松弛性质:
图G从源节点s到节点uk的任意一条最短路径p=<v0,v1,v2,…,vk>,图G在初始化后,在进行一系列松弛操作,其中包括<v0,v1><v1,v2><v2,v3>…<vk-1,vk>的次序进行松弛操作后,我们可以得到源节点到vk的最短路径(权重),并且所得的值会一直保持成立。该性质的成立与其他边的松弛操作及顺序无关。
路径松弛性质的证明可以用归纳法证明:
第一步,源节点s到s的最短路径权重就是0,初始化后将不会改变;
归纳步,假定s到vi-1的最短路径权重为k,我们经过(vi-1,vi)松弛操作后,必然有s到vi的最短路径(权重)就可以得到了(收敛性质),并且其他操作不会改变这个结果。
因为经过|G.V|-1的循环的松弛操作,必定包括<v0,v1><v1,v2><v2,v3>…<vk-1,vk>的次序的松弛操作,因而Bellman-Ford算法合理的。
下面给出的C++实现:
1 typedef struct Edge { 2 int u,v; 3 int weight; 4 }Edge; //边集来描述图 5 6 7 bool Bellman_Ford(Edge *edge,int *dist,int *prev,int vex,int v,int edgenum) 8 { 9 bool flag=1; 10 for(int i=0;i<vex;i++) 11 { 12 dist[i]=MAX; //源节点到每个节点的距离 13 } 14 dist[v]=0; 15 for(int i=0;i<edgenum;i++) 16 { 17 if(edge[i].u==v) 18 { 19 dist[edge[i].v]=edge[i].weight; //初始化dist数组 20 prev[edge[i].v]=v; //设置前驱节点 21 } 22 } 23 24 for(int i=1;i<vex;i++) //松弛操作 25 { 26 for(int j=0;j<edgenum;j++) 27 { 28 if(dist[edge[j].v]>dist[edge[j].u]+edge[j].weight) 29 { 30 dist[edge[j].v]=dist[edge[j].u]+edge[j].weight; 31 prev[edge[j].v]=edge[j].u; 32 } 33 } 34 } 35 for(int j=0;j<edgenum;j++) //检验是否含有权重为负的环路 36 { 37 if(dist[edge[j].v]>dist[edge[j].u]+edge[j].weight) 38 flag=0; 39 } 40 return flag; 41 }