单源最短路径(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 最短路

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

相关文章推荐

单源最短路径问题[Dijkstra实现]

单源最短路径问题[Dijkstra实现]一、问题 带权有向图G(E,V), 找出从给定源顶点s到其它顶点v的权最小路径。 “最短路径” = 最小权二、问题求解:求1到5的最短路径值? 三、执行过程: ...

算法导论 单源最短路径

本章中有三个最短路径算法: 1、Bellman-Ford算法:解决的是一般情况下的单源最短路径问题,可适用于边的权重为负值,且有环路的情况,算法返回一个bool值,表明是否存在一个从源结点可以到达的权...

算法导论-24.2-有向无回路图中的单源最短路径

一、概念 二、代码 #include #include using namespace std; #define N 6 #define M 10 //边结点结构 stru...

算法导论 - 第24章 单源最短路径

单源最短路径的算法有两种:Bellman-Ford算法和Dijkstra算法。求最短路径,如果最短路径上存在weight小于0的cycle,则不存在最短路径。因为只要在这个cycle上多循环几次,该路...

单源最短路径(算法导论24章)

最短路径算法通常依赖最短路径的

算法导论 第24章 单源最短路径

最短路径的定义     在最短路径问题中,给出的是一个带权有向图G=(V,E),加权函数w:E->R为从边到实值的映射。路径p = 的权是其组成该路径的各边的权值之和: 定义从u到v的最短路径的...

算法导论 ch24 单源最短路径 Bellman-Ford

基本算法实现如下, /home/a/j/nomad2:cat bellman.CPP #include using namespace std; typedef int typec; cons...

算法导论 | 第24章 单源最短路径

零、前言

[算法导论] 单源最短路径 - Dijkstra 学习笔记

Dijkstra采用的是贪心策略,为什么这种策略是正确的呢?下面我来简单说一下我的学习心得。 首先假设起点为s,终点为e。δ(s,e)代表两点的最短路径权值。ω(s,e)代表δ(s,e)经过的结点...

图--有向无负权回路的单源最短路径 DAG 算法导论p365

DAG_SHORTEST_PATHS该算法是对某加权的有向无回路图(即DAG图),特指的是无负权回路图,即可是带负权边的图或是带正值的回路图。时间是(V+E) 实现步骤是: 1、建立有向邻接表 2、然...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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