• 单源最短路径


    一、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 } 
  • 相关阅读:
    腾讯蓝鲸cmdb部署
    Linux 检测 tls
    MongoDB主从
    rundeck配置salt-api
    salt-api 配置使用
    rsync同步备份
    su与su -,sudo 的区别
    Redis 迁移 DB; move key db
    数据结构与算法面试题80道(36)
    数据结构与算法面试题80道(35)
  • 原文地址:https://www.cnblogs.com/kiplove/p/6941945.html
Copyright © 2020-2023  润新知