最短路径算法(Dijkstra)

一、前言

  最短路径算法,顾名思义就是求解某点到某点的最短的距离、消耗、费用等等,有各种各样的描述,在地图上看,可以说是图上一个地点到达另外一个地点的最短的距离。比方说,我们把地图上的每一个城市想象成一个点,从一个城市到另一个城市的花费是不一样的。现在我们要从上海去往北京,需要考虑的是找到一条路线,使得从上海到北京的花费最小。有人可能首先会想到,飞机直达啊,这当然是时间消耗最小的方法,但是考虑到费用的高昂,这条线路甚至还不如上海到北京的高铁可取。更有甚者,假设国家开通了从上海到西藏,再从西藏到兰州等等城市经过万般周折最后到达北京的一条线路,虽然要需要经历较长一段时间,但是价钱相比前二者非常实惠(假设只要一块钱,便能跑大半个中国,领略多省风光),单从省钱的角度看来,自然最后这条是可取的。这就是我们在这里所说的单源最短路径。我们接下来的篇幅中将去讲解所有边权值为非负的有向图的单源最短路径,由于无向图相当于变相的有向图,在这里就不做解释,留作读者自行推广。

二、概念

这里我们讲解最短路径,需要掌握几个基本的概念:

对于有向图G=(V,E),权值函数W: E→R(即每条边的权值都为一个实数)

1、路径

      这里写图片描述

    表示从v1到vk的一条路径,它的权值为:

      这里写图片描述

2、最短路径:从u到v的一条路径,使w(p)最小,w(p)。

3、最短路径权值:
这里写图片描述

 注意,最短路径可能不存在:

    (1)存在负权回路,例如:
    这里写图片描述
可以看出,存在v1到v6的负权回路,它的权值为-3,如果我们想找从u到v的最短路径,那么无限循环地走这个负权回路可以使最短路径越来越小,最后达到负无穷,     那么就说明找不到从u到v的最短路径。

(2)不存在从u到v的路径,这个是肯定不会存在最短路径的。

三、最优子结构

  我们不难发现,求解源点到某一顶点的最短路径,其实不比求解源点到所有顶点的路径简单。这个时候我们要引入全局的概念,能不能找出所有的顶点的最短路径,然后再去查看到目标点的最短路径呢?很多人就会想到动态规划这一思想,说道动态规划,自然我们首先要考虑的问题是最优子结构。

  最短路径满足最优子结构性质:最短路径的子路径是最短路径。
  这里写图片描述
前提:u到v是最短路径。

假设:x到y不是最短路径,那么存在一条更短的路径从x到y(假设为下面的弯箭头),这样,删去原路径中从x到y的路径,用新找到的路径替代(弯箭头),那么就得到       了一条比u到v的权值更短的路径,这与前提u到v是最短路径相矛盾,因而x到y是最短路径,即最短路径满足最优子结构性质。

引入三角不等式的概念:(从u到v的最短路径权值,小于等于从u到x的最短路径权值加上从x到v的最短路径权值)
这里写图片描述

四、单元最短路径问题

 对于图G= (V, E),给定源点s,找到从s到所有顶点v的最短路径这里写图片描述由于在本文中我们讲解Dijkstra算法,需要假设没有负权值,即:这里写图片描述 因而,只要路径存在,便存在最短路径。

五、Dijkstra算法思想

 Dijkstra是非常非常著名的计算机科学家,可能很多人对他的了解只在他的单元最短路径算法上,对操作系统了解的人可能还了解他的银行家算法,在方法学领域还有goto有害论等等。当然要讲他的其他杰出贡献,那就要把话题扯远了,今天我们只讲讲Dijkstra算法的思想,然后在后面给出它的实现过程,必要的给出其正确性的证明(Dijkstra在程序正确性证明领域发明了最弱前置谓词证明方法,不过我们不会用他的方法证明他老人家的程序的正确性,不然就扯到了十万八千里-。-)。

  算法思想:

   (1)在任意时刻,我们都要得到从源点到所有顶点的估算距离,并维持一个顶点集合S,若顶点v在S中,则说明从源点到v的最短路径已知;

   (2)在每一次将不在S中的顶点v加到S中去时,总是选择从源点到v的估算距离最小的;

   (3)顶点v加入S中之后,对于所有与v相邻的顶点(不属于S),更新它们的估算距离。

  由(2),我们看到了贪心的影子,在每次选择时,我们总是想选择花费最小的,正常人都会这样去想,至于为什么这样选,这样选对不对,我们将在后面进行证明。

  伪代码如下:
  


Dijkstra(G, W, s)      //G表示图,W表示权值函数,s表示源顶点
d[s] ←0          //源点到源点最短路为0
 for each v ∈ V - {s}  //3-8行均为初始化操作
   do d[v]←∞
      parent[v]←NIL

S←∅        
Q←V        //此处Q为优先队列,存储未进入S的各顶点以及从源点到这些顶点的估算距离,采用二叉堆(最小堆)实现,越小越优先
while Q≠∅
    do u←Extract-Min(Q)  //提取估算距离最小的顶点,在优先队列中位于顶部,出队列,放入集合S中
    S←S∪{u}
   for each v ∈ Adj(u)  //松弛操作,对与u相邻的每个顶点v,进行维持三角不等式成立的松弛操作。
      do if d[v] > d[u] + w(u, v)
        then d[v] = d[u] + w(u, v)  //这一步隐含了更新优先队列中的值。
            parent[v]←u      //置v的前驱结点为u

六、简单例子说明

初始情况:
这里写图片描述

第一次松弛,选取A顶点:
这里写图片描述

第二次松弛,C的估算距离最小,选取C顶点:
这里写图片描述
第三次松弛,E的估算距离最小,选取E:
这里写图片描述
第四次松弛,B的估算距离最小,选取B:
这里写图片描述
第五次松弛:(最后一个点,完成)
这里写图片描述
经过所有的松弛操作之后,我们就得到了所有顶点的最短路径(表格中红字部分)。

如果加上对parent[]进行的操作,我们还可以得到一棵最短路径树,这个读者可以自行推广。

七、代码实现

      #include <iostream>

#include <queue>

#include <cstdio>

#include <vector>

#include <cstring>

#define INF 0x3f3f3f3f

using namespace std;

const int MAX=2500;

int dis[MAX];

int n,m;//节点数,与边数

struct node

{

    int t,v;

};

struct ke

{

    int me,w;

    friend bool operator<(ke n1,ke n2)

    {

        return n1.w>n2.w;

    }

};

vector<node>edge[MAX];



void dijkstra(int nn)

{

    ke a;

    memset(dis,INF,sizeof(dis));

    priority_queue<ke>Q;

    dis[nn]=0;

    a.me=nn,a.w=0;

    Q.push(a);

    while(!Q.empty())

    {

        ke p=Q.top();

        Q.pop();

        for(int i=0;i<edge[p.me].size();++i)

        {

            int to=edge[p.me][i].t;

            int v=edge[p.me][i].v;

            if(dis[to]>dis[p.me]+v)

            {

                dis[to]=dis[p.me]+v;

                ke a;

                a.me=to,a.w=dis[to];

                Q.push(a);

            }

        }

    }

}

int main()

{

    while(scanf("%d %d",&m,&n)!=EOF)

    {

        int fo,to,v;

        node a;

        for(int i=0;i<MAX;++i)

            edge[i].clear();

        for(int i=0;i<m;++i)

        {

            scanf("%d %d %d",&fo,&to,&v);

            a.t=to,a.v=v;

            edge[fo].push_back(a);

            a.t=fo,a.v=v;

            edge[to].push_back(a);

        }

        dijkstra(1);

//        for(int i=1;i<=n;++i)

//        printf("%-3d",dis[i]);

            printf("%d\n",dis[n]);

    }

    return 0;

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值