单源最短路径---Dijkstra算法

有这样一道题:在一个图(如图所示)中,一共有四个点:1 2 3 4

这四个点之间各有相连,且每条边都有自己的权值。现在小明在点1上,

他想要到3去,请问最短路径是多少。


图这里写图片描述


很容易得到该图的邻接矩阵。我们建立一个二维数组a。a[i][j],i表示
起点,为行,j表示终点,为列。将相应的权值传入其中,如果从一个
点到另一个点不通,就认为其权值为无限

例如(1-》2)为2,则a[1][2]=2;
而因为2到1不通,就令a[2][1]=∞;
另外a[1][1]之类的起点终点相同的都设为了0;

对这种求点i到点j的最短路径的问题,很难直接求得。通常是要多求一
些多余的量才能得到结果。因为我们必须要做好遍历全图的准备才能比
较或是寻找出其最短路径。


这时候我们来介绍两种算法:

  • 一种是Dijkstra算法单源最短路径算法
  • 一种是Floyd多元路径最短算法

今天只说单源最短路径算法
所谓单源,即求从一个点出发,到其他各点的最短路径,也就是说
如果这个图有n个点,我们要求n-1个路径。
对一个图G来说,它的点集为V,我们要做的就是求出从起点v到V中其
余各点的最短路径。

首先介绍单源最短路径的核心算法:
 起点是v,我们知道在整个图中,以v为起点的路径有很多条,有长的。有短的;有的简单,只有一段弧,只有两个点:起点。终点。有的复杂,有好几段弧,起点终点之间隔了好几个节点。
 我们要做的就是先找到所有这些从v引出的路径中的最短的那一条(v,…,j1)。
 然后通过某种算法下文中将会介绍)再找出仅长于最短路径的次短路径(v,…,j2),找到以后,(注意:我们把每一次找到的次短路径的终点记录下来,如果下一次找寻的时候遇到以这些点为终点的路径就忽略掉),再找下一条次短路径,再找,再找……
  这里有一个辨析:

按照上述步骤,我们依次找到了整张图中(具有不同终点的)最短的几条路径:
(v,…,j1)  (v,…,j2)   (v,…,j3)  ……  (v,…,j) ……

这里,如果我们称图中所有以v为起点 以j为终点的路径为j族,那么我们每找到一条次短路径 (v,…,j),这条路径(v,…,j)一定是j族中最短的那条。
为什么呢?按照我们的找法,我们实际上是把整张图里的所有以v为起点的路径按照从短到长的顺序排列起来,然后从第一条路径开始往后检索,如果我们从前到后查找时第一次遇到了以j为终点的路径,记为路径1,接下来往后,我们遇到的每一条属于j族的路径都忽略,从而我们得到了之前的序列。显然路径1就是j族中最短的那一条,也是我们的目标路径之一。


我们 所要求的是点v到其余各点的最短路径,其余各点有n-1个,也就是说,我们有n-1个终点,对应n-1个族。对每个族,都有一个对应的最短路径。我们要做的就是求出每个族的最短路径。

之前说到,我们要把每一次找到的次短路径的终点记录下来,下一次遇到的时候就忽略掉,这可以保证最终的得到的序列都属于不同的族,我们可以建立一个点集S,每找到一条次短路径,就将其终点并入集合S中,下一次找寻路径的时候拿终点与点集S对比,决定是否保留。

就这样,每一次我们都找到一个以j为终点的最短路径,而每一次的终点j又都不同,当找了n-1次时,就完成了单源最短路径查找。


我们已经了解完了整体的思路,所以现在的关键性问题就是如何完成对次短路径的查找。也就是上文中提到的某种算法,它到底是什么呢?

  • 第一步:

      一个图中有许多条路径,我们现在在图G中找到以v为起点的最短的那一条路径(v,j)。显然我们可以很容易的的发现,这条最短路径一定是与起点v直接相连的弧,j是v的邻结点。如图所示;
      

  • 第二步:
     接下来,我们来找次短路径,也就是以v为起点的下一条最短的路径。如图所示我们会发现,次短路径要么是弧(v,k)(注:k是v相邻的点,除j外),要么是弧(v,j,k)(k是j紧邻的点)。
     这样,我们通过比较可以求出最短的那一条路径(v,k);

然后我们就会猜想,如果按照第二步的做法一直重复下去,不就能依次找到次短路径了吗?
但是,事实上,当我们尝试之后会发现,随着不断地重复查找次短路径的过程,我们不能单纯的像第二次查找那样,因为每一次的查找都会衍生很多分支。使得查找变得复杂。


怎么解决这个问题呢?为了方便理解我引入了一个观测域的概念。

首先看一些定义:

  • 点集V 图中所有点的集合
  • 点集S  已经找到相应最短路径的终点集合
  • 数组D[n] 存储观测域内能观测到的最短路径,算上起点一共n个数值 。比如D[k]对应在观测域中能观测到的,k族中的最短路径。
  • 邻接矩阵a[n][n]  存储着相应的权值

如图所示:刚开始时,我立足于v点,观测域为v点的四周,即v的所有邻接点。由邻接矩阵a,更新数组D。此时D中的数为(v,k)的权值(k为v的邻接点)。
 

这里写图片描述
随后,我在我观测域中找寻最短的那一条路径(v,j)也就是查找数组D中最小的数,并将j收入集合S中。

如图所示:现在我想找次短路径,现在我清楚,这条次短路径要么是(v,k)(k为观测域中的点,但不属于S)的最短边,要么是通过j点的弧(v,j,k)(k为j的邻接点,但不属于S)的最短边。

这里写图片描述
我们干脆将j的邻接点全都纳入观测域,同时更新数组D,通过数组D找出次短边(v,j),再将j压入S中。
这里写图片描述
不断重复该步骤,直到所有的点都入了S,就完成了查找,这时数组D
D[k]就是从v到k的最短路径的长度。如果你想知道具体的路径时只需加个栈就行了。


所以完整的步骤是这样的:

  • 第一步:
    初始化点集S,将起点v收入S中。初始化数组D:D[k]=a[v][k];

  • 第二步:找寻次短路径。即查找数组D找出观测域中最短路径(v,j):D[j]=min(D[k]|k不属于S)。将j压入点集S中

  • 第三步:将j的邻接点并入观测域,即用j的邻接点更新数组D:

     如果D[k]>D[j]+a[j][k]     (k为j邻接点,k不属于S)
     令D[k]=D[j]+a[j][k]
     如果D[k]>D[j]+a[j][k]     (k为j邻接点,k不属于S)
     就不做操作
    

然后不断重复第二步和第三步直到找到全部节点为止。

具体实现为:

#include<iostream>
using namespace std;
#define BUTONG 1000000          //  无限大为不通 
#define NUM 4      //d点数为4 
int a[NUM][NUM]={
        0,2,6,4,
        BUTONG,0,3,BUTONG,
        7,BUTONG,0,1,
        5,BUTONG,12,0
    };
#define OK 1
#define ERROR 0                 
typedef int status;

bool finish(bool *S,int n)          //是否完成 找寻 
{
    for(int i=0;i<n;i++)
    {
        if(!S[i])
        return false;
    }
    return true; 
}

status djs(int n,int t)//a 为数组 n 为点的个数,v为起始点 
{
    //初始化数组v
    int D[n];
    for(int i=0;i<n;i++)
    D[i]=a[t][i];
    D[t]=0;
    //初始化已访问数集S;
    bool S[n];

    for(int i=0;i<n;i++)
    S[i]=false;

    S[t]=true;

    int j=0;
        int min=BUTONG;

    while (!finish(S,n))
    {   j=0;
        min=BUTONG;

        for(int i=0;i<n;i++)        //找到观测域中最短路径(v,j) 
        {
            if(S[i]) continue;

            if(min>D[i]) 
            {min=D[i];j=i;}     
        }   

        S[j]=true;      //将j纳入点集S中 

        for(int i=0;i<n;i++)        //更新观测域 
        {
            if(S[i]) continue;
            if(D[i]>D[j]+a[j][i])
            D[i]=D[j]+a[j][i];      
        }


    }

    for(int i=0;i<n;i++)                //输出 
    {
        printf("最短路径是(v,%d)长度是%d\n",i,D[i]);
    }
    return OK; 
}
int main()
{

    djs(NUM,1);
    return 0;

} 
  • 28
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: Dijkstra算法是一种用于解决单源路径问题的算法。它的基本思想是从起点开始,逐步扩展到其他节点,每次选择当前距离起点最近的节点,并更新与该节点相邻的节点的距离。通过这种方式,可以找到起点到其他节点的最路径。Dijkstra算法的时间复杂度为O(n^2),但是可以通过使用堆优化来将其优化到O(nlogn)。 ### 回答2: Dijkstra算法是一种解决单源路径问题的贪心算法,其思想是利用“松弛”操作来不断更新当前点到源点的最距离,但前提是所有边的权重非负。如果有负权边,则需要使用Bellman-Ford算法。 首先,我们需要定义一个数组dis数组,用于存储源点s到各个点的最距离。dis[s]初始为0,其他点初始为无限大。接着,我们需要维护一个集合S,表示已经求出最路径的点的集合。将源点s加入集合S中。 对于每个未加入S的点v,我们通过选择其它点到源点s的最路径中的一个点u,然后将dis[v]更新为dis[u] + w(u,v),其中w(u,v)表示边(u,v)的权重。具体地,这个操作称为“松弛”操作。 在松弛操作中,我们需要比较dis[u] + w(u,v)和dis[v]的大小,如果前者更小,则更新dis[v]的值为dis[u] + w(u,v)。 重复执行以上操作,直到所有的点都加入到集合S中。最后dis数组中存储的就是源点s到所有点的最距离。 Dijkstra算法可以用堆优化,时间复杂度为O(mlogn),其中n表示图中的点数,m表示边数。Dijkstra算法也可以应用于稠密图,时间复杂度为O(n^2)。 总之,Dijkstra算法是一种经典的求解单源路径问题的算法,其实现简单,效率高,被广泛应用于路由算法和图像处理等领域。 ### 回答3: Dijkstra算法是一种在加权有向图中寻找从源节点到其他节点的最路径的贪心算法。该算法基于其它路径加权节点的已知最路径去更新更长路径的信息直到找到从源节点到目标节点的最路径。在整个计算过程中,Dijkstra算法需要维护一个待处理节点集合和一个距离源节点的最路径数组。 算法的具体实现如下: 1. 初始化源节点及其距离为0,其他节点的距离为无穷大。 2. 将源节点加入到待处理节点集合中。 3. 对于源节点的所有相邻节点,更新它们距离源节点的最路径。如果当前路径小于之前已知的最路径,则更新最路径数组。 4. 遍历待处理节点集合中除源节点外的节点,选择距离最近的节点作为当前节点,并将它从待处理机集合中移除。 5. 对于当前节点的所有相邻节点,更新它们距离源节点的最路径。如果当前路径小于之前已知的最路径,则更新最路径数组。 6. 重复步骤4和5,直到待处理节点集合为空或者目标节点已经被遍历。 Dijkstra算法的时间复杂度为O(n^2),其中n为节点数,由于它是贪心算法,只能处理非负权重的图,否则可能会陷入死循环。但是,Dijkstra算法单源路径问题的最优解,因此在处理小规模的图时效果很好。在处理大规模图时,需要使用其他高效的算法,如A*算法、Bellman-Ford算法等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fuckguidao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值