单源最短路径

Posted kiplove

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单源最短路径相关的知识,希望对你有一定的参考价值。

一、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]=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 } 

 

以上是关于单源最短路径的主要内容,如果未能解决你的问题,请参考以下文章

单源最短路径Dijkstra算法的思想详细步骤代码

图文解析 Dijkstra单源最短路径算法

单源最短路径

单源最短路径

单源最短路径

单源最短路径 djkstra