单源最短路径(Single Source Shortest Path)--《算法导论》

原创 2016年08月28日 21:12:44

关于最短路径的描述请参考维基百科Shortest Path

简单总结一下算法导论上描述的计算从单一节点源到图中每一节点的最短路径算法,Bellman-Ford算法及其优化版本spfa,以及对权重非负的图的Dijkstra算法。

基本操作

初始化节点源
设节点v到节点源s的距离为v.d求最短路径之前进行如下初始化操作

INITIALIZE_SINGLE_SOURCE(G,s)
for each veterx vG.V
        v.d= INF
        v.p= NIL
s.d = 0

松弛

RELAX(u,v)
if  v.d>u.d+w(u,v)
      v.d=u.d+w(u,v)
      v.p=u

在介绍最短路径算法之前先简单介绍一些最短路的性质,但这里不做严格证明,证明详见算法导论。

最短路径的性质

三角不等式: δ(s,v)δ(s,u)+w(u,v)

环路:
最短路径首先是不允许有负环的,因为如果有负环,那在负环路径上的节点可以绕着负环无穷多圈从而δ(s,v)=。那么可不可以有正环呢,答案是否定的,可以用反证法证明,如果有正环,那么显然去掉正环后的路径的权值肯定比带正环的路径更小。所以最短路径是不包含环路的,而且单源节点到所有节点的最短路径构成一颗最短路径树。

子路径性质:最短路的子路径也是最短路,即如果路径sv的最短路经过节点u,则路径p:susu的最短路,这可以用“剪贴法”证明一下。

收敛性质:给定一个带权重的有向图,G(V,E),设路径p:suv 为定源节点sv的一条最短路径,并且在进行松弛操作以前已经,按INITIALIZE_SINGLE_SOURCE(G,s)进行初始化,且有u.d=δ(s,u),则在松弛操作RELAX(u,v)之后,v.d=δ(s,v)

路径松弛性质:Gs到任意节点vk的最短路径为p=<v0,...,vk1,vk>,并且按算法INITIALIZE_SINGLE_SOURCE(G,s)进行初始化,则在进行一系列松弛操作,包括对边(v0,v1),...,(vi1,vi),..,(vk1,vk)所列次序进行松弛操作以后有vk.d=δ(s,vk).这可以用归纳法证明一下。

这些性质是后续介绍的算法的基础,特别是其中的松弛操作。

Bellman-Ford

伪代码:

Bellman-Ford(G,s)

INITIALIZE_SINGLE_SOURCE(G,s)

for i=1 to V-1

        for each edge G.E

                RELAX(u,v)

for each edge (u,v) G.E

        ifv.d>u.d+w(u,v)

        return FALSE

returnTRUE

时间复杂度O(VE),该算法可以求出给定源节点s,到任意节点的最短路径,并在图中存在负环是返回false

但是该算法时间复杂度太大,通常我们使用它的优化版本。可以看到Bellman-ford算法,对每条边都松弛V1遍,但我们可以看到其中大部分时候所做的都是无用功,所以可以用一个队列来优化,减少不必要的松弛

SPFA(Shortest Path Faster Algorithm)

伪代码

spfa(G,s)

INITIALIZE_SINGLE_SOURCE(G,s)

Q.inqueue(s)

inq[s] = TRUE

whle !Q.empty()

          vertex u = Q.pop()

          for each edge (u,v) AdjEdge[u]

          if  RELAX(u,v) inq[v] ==FALSE

                 Q.inque(v)

                 if ++ v.cnt>|V|

                        return FALSE
return TRUE

c++代码

bool spfa(int s)
{
   // memset(cnt,0,sizeof(cnt[0])*nv);//记录数组,用于检测负环真正使用时若题目中告诉没有负环可不用
    memset(d,INF,sizeof(d[0])*nv);
    memset(inq,false,sizeof(inq[0])*nv);
    d[s] = 0;
    memset(p,-1,sizeof(p[0])*nv);
    queue<int> q;
    q.push(s);
    inq[s] = true;

    while(!q.empty())
    {
        int u = q.front();q.pop();
        inq[u] = false;
        for(int i=first[u] ; i!=-1; i = nt[i])//用边集数组储存
        {
            Edge &e = edges[i] ;
            //松弛
            if(d[e.to]>d[u]+e.w)
            {
                d[e.to] = d[u]+e.w;
                p[e.to] = u;
                if(!inq[e.to])
                {
                    q.push(e.to);
                    inq[e.to] = true;
                    //if(++cnt[e.to]>nv)return false;
                }
            }
        }

    }
  return true;

}

可以“感受”到该算法的复杂度会低于Bellman的算法,实际证明出来是O(kE),一般来说,k是一个比|V|低很多的常数。用边集数组储存也可以优化内存

Dijkstra

这个算用于计算权重为正数的SP的时候会优于SPFA,用一般的二叉堆储存只需O(ElgV)而用斐波那契堆只需O(VlgV)

算法描述
用一个优先队列储存所有节点,每次取出v.d最小的节点,松弛他的邻接顶点,依次下去直到队列为空。

伪代码

INITIALIZE_SINGLE_SOURCE(G,s)

Q(v)

while  !Q.empty()

vertex u = Q.pop()

for each edge (u,v)AdjEdge[u]

            RELAX(u,v)

c++代码

void Dijkstra(int s)
{
    memset(d,INF,sizeof(d[0])*nv);
    d[s] = 0;
    memset(p,-1,sizeof(p[0])*nv);
    memset(vis,fasle,sizeof(vis))
    priority_queue<pii,vector<pii>,greater<pii> > q;//pair默认先比较第一个分量,{d,s}
    q.push(pii(0,s));
    vis[s] = true;
    while(!q.empty())
    {
        int u = q.top().second;q.pop();
        if(vis[u])continue;
        vis[u] = true;
        for(int i = first[u] ; i!=-1 ; i = n[i])
        {
            Edge &e = edges[i];
            if(d[e.to]>d[u]+e.w)
            {

                d[e.to] = d[u]+e.w;
                //p[e.to] = G[u][i]//保存边
               p[e.to] = u;//最短路径树中的父节点
                q.push(pii(d[e.to],e.to));
            }
        }
    }
}

因为STL里面的priority_queue,不支持对e.to的优先级实时更新,所以采用每次更新节点后重新放入的方式。为了防止多次重复访问采用用一个标记数组标记的做法记录是否访问过。

总结

对于一般的有负权重的最短路问题最好使用spfa,而只有正权重的图,使用Dijkstra往往更快。而对于一般的有向无环图直接使用BFS加上松弛已经足够。

附上一个代码测试题
HDU 2544 最短路

版权声明:本文为博主原创文章,未经博主允许不得转载。

Graph: Single Source Shortest Path(TODO)

Single Source Shortest Path(SSSP) 问题定义:最短路径问题有很多种类,一般最开始都是从SSSP问题开始研究,它最基本最简单,而且很多问题可以化归为SSSP,这个问题的...
  • whb923
  • whb923
  • 2015年01月08日 13:18
  • 558

算法导论problem 24-5 Gabow's algorithm for single source shortest path

源码:有部分测试用没删除。 /* target : Gabow's scaling algorithm for single-source shortest paths   * descripti...

贪心算法实例 单源最短路径 Dijkstra算法(c++实现)

基本思想:设置顶点集合S并不断地做贪心选择来扩充这个集合。一旦S包含了所有V中的顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。 顶点V是源。 c是一个二维数组,c[i][j]表示边(...
  • catkint
  • catkint
  • 2016年04月23日 09:15
  • 2186

Dijkstra算法(单源最短路径) C++

Dijkstra算法(单源最短路径)       单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。 一.最短路...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

单源最短路径—Dijkstra算法(C++)

最近复习图算法,练练手 先拿Dijkstra算法开刀吧以下是C++代码包含:Dijkstra算法函数(返回源节点到其余节点之间的最短路径)、路径打印输出函数PS:本人只喜欢用vector,不喜欢用原...

<C/C++图>单源最短路径:Dijkstra算法

一,Dijkstra算法基本概念 Dijkstra算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。 该算法...

DijkStra最短路径的C++实现与输出路径

一个点(源点)到其余各个顶点的最短路径。也叫做“单源最短路径”Dijkstra。 Dijkstra的主要思想:每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路...

单源点最短路径 c++实现 分支限界算法

问题描述:在一个网(带权图)中,从指定节点出发,找到到各个节点的最短路径   分支限界思想: 一个问题的解空间可以用一棵树来表示,比如此题,在此题的解空间树中,根是指定节点,任何一个...
  • longzuo
  • longzuo
  • 2015年05月25日 14:06
  • 778

单源最短路径(Dijkstra)(C++)

寻找起始点与其余点的最短路径向量。 例如:        输入:                    1     (一组测试数据)                    5     7  (...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:单源最短路径(Single Source Shortest Path)--《算法导论》
举报原因:
原因补充:

(最多只允许输入30个字)