草庐IT

图解迪杰斯特拉(Dijkstra)最短路径算法

小白还在写代码 2023-04-21 原文

往期文章目录

【干货满满!】【最小生成树】Prim算法

                         【最小生成树】Kruskal算法


目录

前言

一、最短路径的概念及应用

二、Dijkstra迪杰斯特拉

1.什么是Dijkstra

2.逻辑实现

总结


前言

  无论是什么程序都要和数据打交道,一个好的程序员会选择更优的数据结构来更好的解决问题,因此数据结构的重要性不言而喻。数据结构的学习本质上是让我们能见到很多前辈在解决一些要求时间和空间的难点问题上设计出的一系列解决方法,我们可以在今后借鉴这些方法,也可以根据这些方法在遇到具体的新问题时提出自己的解决方法。(所以各种定义等字眼就不用过度深究啦,每个人的表达方式不一样而已),在此以下的所有代码都是仅供参考,并不是唯一的答案,只要逻辑上能行的通,写出来的代码能达到相同的结果,并且在复杂度上差不多,就行了。


一、最短路径的概念及应用

在介绍最短路径之前我们首先要明白两个概念:什么是源点,什么是终点?在一条路径中,起始的第一个节点叫做源点;终点:在一条路径中,最后一个的节点叫做终点;注意!源点和终点都只是相对于一条路径而言,每一条路径都会有相同或者不相同的源点和终点。 

而最短路径这个词不用过多解释,就是其字面意思:在图中,对于非带权无向图而言,从源点到终点边最少的路径(也就是BFS广度优先的方法);而对于带权图而言,从源点到终点权值之和最少的路径叫最短路径;

最短路径应用:道路规划;

我们最关心的就是如何用代码去实现寻找最短路径,通过实现最短路径有两种算法:Dijkstra迪杰斯特拉算法和Floyd弗洛伊德算法,接下来我会详细讲解Dijkstra迪杰斯特拉算法;

二、Dijkstra迪杰斯特拉

1.什么是Dijkstra

Dijkstra迪杰斯特拉是一种处理单源点的最短路径算法,就是说求从某一个节点到其他所有节点的最短路径就是Dijkstra;

2.逻辑实现

在Dijkstra中,我们需要引入一个辅助变量D(遇到解决不了的问题就加变量[_doge]),这个D我们把它设置为数组,数组里每一个数据表示当前所找到的从源点V开始到每一个节点Vi的最短路径长度,如果V到Vi有弧,那么就是每一个数据存储的就是弧的权值之和,否则就是无穷大;

我们还需要两个数组P和Final,它们分别表示:源点到Vi的走过的路径向量,和当前已经求得的从源点到Vi的最短路径(也就是作为一个标记表示该节点已经加入到最短路径中了);

 那么对于如下这个带权无向图而言,我们应该如何去找到从V0到V8的最短路径呢;

在上文中我们已经描述过了,在从V0到V8的这一条最短路径中,V0自然是源点,而V8自然是终点;于是我根据上文的描述具现化出如下的表格;

 在辅助向量D中,与源点V0有边的就填入边的权值,没边就是无穷大;

构建了D、P和Final,那么我们要开始遍历V0,找V0的所有边中权值最短的的边,把它在D、P、Final中更新;

具体是什么意识呢?在上述带权无向图中,我们可以得到与源点有关的边有(V0,V1)和(V0,V2),它们的权值分别是1和5,那么我们要找到的权值最短的的边,就是权值为1 的(V0,V1),所以把Final[1]置1,表示这个边已经加入到最短路径之中了;

而原本从V0到V2的距离是5,现在找到了一条更短的从V0 -> V1 -> V2距离为4,所以D[2]更新为4,P[2]更新为1,表示源点到V2经过了V1的中转;

继续遍历,找到从V0出发,路径最短并且final的值为0的节点。因为经过节点V1的中转,源点到V3和V4有了路径,从源点到V3的距离是1+7==8,到V4的距离是1+5==6,把它们在D中更新;再以V1为中心,去找与V1有关的边中权值最短的边,可以得到此时V0到V2的距离为4,是我们要找的边,于是把V2加入到最短路径中;

重复上述步骤,遍历以V2为中心的顶点,与V2有关的顶点有V0、V1、V4、V5,其中0和1已经加入到最短路径中了所以不管它们,而V2到V4的路径最短,所以将Final[4]置1;那么V0到V4原来的距离是6,而现在经过V2的中转,V0到V1到V2到V4的距离为5,所以更新D、P;

接着找以V4为中心,与V4有关的边:V3、 V5、V6、 V7更新D、P,原本V0到V3是距离8,现在是更新为7,且我们可以发现这条边的权值最小,所以把Final[3]置1;

 现在V3最短,所以以V3为中心,到V6的距离最近,所以更新D[6]、P[6]和Final[6];

  现在V6最短,所以以V6为中心,到V7的距离最近,所以更新D[7]、P[7]和Final[7];

现在V7最短,所以以V7为中心,到V8的距离最近,所以更新D[8]、P[8]和Final[8];

 至此,源点和终点都被加入到了最短路径当中,Dijkstra算法结束;那么我们从P[8]开始从后往前推,就可以得到这个带权无向图的从V0到V8的最短路径;

如图所示从P[8]开始从后往前推算,数组P中的值就是在最短路径中该节点的上一个节点;可以得到:V8<-V7<-V6<-V3<-V4<-V2<-V1<-V0;即如下图所示:

 3.代码实现

逻辑上理顺了,那么问题来了,代码要怎么写?因为是带权的无向图,所以这里我们还是以邻接矩阵去构建图(关于邻接矩阵的详细概念在我往前的文章里有),这里还是简单说一下,如果Vi到Vj有边,那么存入的就是这条边的权值,如果没有边就存入一个不可能的数表示无穷大(如65535)

void creategrahp(AdjMatrix* G)
{
    int n, e;//n代表顶点数 e代表边数
    int vi, vj;//vi vj代表边的两个顶点对
    int w;//表示边的权值
    printf("要输入的顶点数和边数\n");
    scanf("%d%d",&n,&e);
    G->numV = n; 
    G->numE = e;
    //图的初始化
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(i == j)
            {
                //一个非带权的图 0代表没有边 1代表有边
                //边不指向自己 即对角线为0
                G->Edge[i][j] = 0;
            }
            else
            {
                //如果是带权的图 初始化为0或者为一个不可能的值
                G->Edge[i][j] = 65535;
            }
        }
    }
    //将顶点存入数组
    for(int i = 0; i < G->numV; i++)
    {
        printf("请输入第%d个节点的信息\n",i + 1);
        scanf("%d", &G->Vertices[i]);
    }
    //输入边的信息
    for(int i = 0; i< G->numE; i++)
    {
        //如果输入的是顶点的值 需要从顶点集中查找对应的下标 
        //如果是带权图 还要输入权的信息
        printf("请输入边的信息Vi,Vj和权值w\n");
        scanf("%d%d%d",&vi,&vj,&w);
        G->Edge[vi][vj] = w;
        //如果是带权图 等于权值
        //如果是有向图 就不对称
        //如果是无向图 矩阵对称
        G->Edge[vj][vi] = w;
    }
}

至于Dijkstra的代码实现也很简单,只要按照上文中逻辑去实现就可以了;我每一步代码都详细写了注释,如果还有不清楚的可以私信问我;

首先我们要初始化D、P和Final;D初始存入与源点有关的边的权值;P一开始可以初始化为0或-1表示没有路径;Final初始化为0表示没有被加入到最短路径的标志;

void ShortPath_Dijkstra(AdjMatrix* G, int v0, P* p, D* d)
{
    int k;//记录当前最短路径的下标
    int final[200];//final[x] = 1 表示已求得的到v0的最短路径
    //初始化DPFinal
    for(int i = 0; i < G->numV; i++)
    {
        final[i] = 0;//初始化为未知状态
        (*d)[i] = G->Edge[v0][i];//如果v0传进的是值 寻找下标
        (*p)[i] = -1;
    }
    final[v0] = 1;
    (*d)[v0] = 0;//自己到自己的路径为0

接下来就是找权值最短的边;用变量K记录找到的边最小的顶点的下标;

//主循环 求每次v0到v的最短路径
    for(int i = 1; i < G->numV; i++)
    {
        int min = 65535;
        //寻找与v0距离最近的顶点
        for(int j = 0; j < G->numV; j++)
        {
            if(final[j] != 1 && (*d)[j] < min)
            {
                min = (*d)[j];
                k = j;
            }
        }
        //把Vk加入到最短路径中
        final[k] = 1;

然后以K为中转,找以K为中心的邻接点到源点的距离,如果这个距离小于原来的距离那么更新D和P;如此循环直到所有的点都被加入到了最短路径中,结束!

 //修正当前最短路径的距离
        //以Vk作为中转,更新以Vk为中心的邻接点到V0的距离
        for(int j = 0; j < G->numV; j++)
        {
            //如果当前找到v的顶点的路径小于原来的路径长度
            if(min + G->Edge[k][j] < (*d)[j] && final[j] != 1)
            {
                //说明找到了更短的路径 修改DP
                (*d)[j] = min + G->Edge[k][j];
                (*p)[j] = k;
            }
        }

总结

全部代码

#include<stdio.h>
#include<stdlib.h>
//最短路径算法 迪杰斯特拉 求有向图G 从某一个顶点开始 到其余各个顶点的最短路径P以及带权长度
//P为前驱顶点的下标 D为从选取的顶点V0到V顶点的最短路径长度

typedef int P[200];//储存最短路径下标
typedef int D[200];//v0到v的最短路径长度和

//邻接矩阵
typedef struct AdjacentMatrix
{
    //顶点集
    int Vertices[200];
    //边集
    int Edge[200][200];
    //顶点数 边数
    int numV, numE;
}AdjMatrix;

void creategrahp(AdjMatrix* G)
{
    int n, e;//n代表顶点数 e代表边数
    int vi, vj;//vi vj代表边的两个顶点对
    int w;//表示边的权值
    printf("要输入的顶点数和边数\n");
    scanf("%d%d",&n,&e);
    G->numV = n; 
    G->numE = e;
    //图的初始化
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(i == j)
            {
                //一个非带权的图 0代表没有边 1代表有边
                //边不指向自己 即对角线为0
                G->Edge[i][j] = 0;
            }
            else
            {
                //如果是带权的图 初始化为0或者为一个不可能的值
                G->Edge[i][j] = 65535;
            }
        }
    }
    //将顶点存入数组
    for(int i = 0; i < G->numV; i++)
    {
        printf("请输入第%d个节点的信息\n",i + 1);
        scanf("%d", &G->Vertices[i]);
    }
    //输入边的信息
    for(int i = 0; i< G->numE; i++)
    {
        //如果输入的是顶点的值 需要从顶点集中查找对应的下标 
        //如果是带权图 还要输入权的信息
        printf("请输入边的信息Vi,Vj和权值w\n");
        scanf("%d%d%d",&vi,&vj,&w);
        G->Edge[vi][vj] = w;
        //如果是带权图 等于权值
        //如果是有向图 就不对称
        //如果是无向图 矩阵对称
        G->Edge[vj][vi] = w;
    }
}


void ShortPath_Dijkstra(AdjMatrix* G, int v0, P* p, D* d)
{
    int k;//记录当前最短路径的下标
    int final[200];//final[x] = 1 表示已求得的到v0的最短路径
    //初始化DPFinal
    for(int i = 0; i < G->numV; i++)
    {
        final[i] = 0;//初始化为未知状态
        (*d)[i] = G->Edge[v0][i];//如果v0传进的是值 寻找下标
        (*p)[i] = -1;
    }
    final[v0] = 1;
    (*d)[v0] = 0;//自己到自己的路径为0

    //主循环 求每次v0到v的最短路径
    for(int i = 1; i < G->numV; i++)
    {
        int min = 65535;
        //寻找与v0距离最近的顶点
        for(int j = 0; j < G->numV; j++)
        {
            if(final[j] != 1 && (*d)[j] < min)
            {
                min = (*d)[j];
                k = j;
            }
        }
        //把Vk加入到最短路径中
        final[k] = 1;
        //修正当前最短路径的距离
        //以Vk作为中转,更新以Vk为中心的邻接点到V0的距离
        for(int j = 0; j < G->numV; j++)
        {
            //如果当前找到v的顶点的路径小于原来的路径长度
            if(min + G->Edge[k][j] < (*d)[j] && final[j] != 1)
            {
                //说明找到了更短的路径 修改DP
                (*d)[j] = min + G->Edge[k][j];
                (*p)[j] = k;
            }
        }
    }
}

int main()
{
    AdjMatrix* G = (AdjMatrix*)malloc(sizeof(AdjMatrix));
    int p[200];
    int d[200];
    creategrahp(G);
    int v0 = 0;
    ShortPath_Dijkstra(G, v0, &p, &d);
    for (int i = 1; i < G->numV; i++)
    {
        printf("v%d - v%d:", v0, i);
        int j = i;
        while (p[j] != -1)
        {
            printf("%d", p[j]);
            j = p[j];
        }   
        printf("\n");
    }

    printf("最短路径长度");
    for (int i = 1; i < G->numV; i++)
    {
        printf("v%d-v%d : %d\n", G->Vertices[0], G->Vertices[i], d[i]);
    }
    system("pause");
    return 0;
}

需要注意的是:DIjkstra迪杰斯特拉算法只能找从某一个顶点开始,到其余各个顶点的最短路径P以及带权长度;那么如果我想要知道任意两个节点之间的最短路径要怎么办呢?大家可以思考一下,我们下期再讲~

有关图解迪杰斯特拉(Dijkstra)最短路径算法的更多相关文章

  1. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  2. ruby-on-rails - Rails - 使用/自定义 URL : '/dashboard' 指定根路径 - 2

    如何使此根路径转到:“/dashboard”而不仅仅是http://example.com?root:to=>'dashboard#index',:constraints=>lambda{|req|!req.session[:user_id].blank?} 最佳答案 您可以通过以下方式实现:root:to=>redirect('/dashboard')match'/dashboard',:to=>"dashboard#index",:constraints=>lambda{|req|!req.session[:user_id].b

  3. ruby - 如何根据长度将路径数组转换为嵌套数组或散列 - 2

    我需要根据字符串路径的长度将字符串路径数组转换为符号、哈希和数组的数组给定以下数组:array=["info","services","about/company","about/history/part1","about/history/part2"]我想生成以下输出,对不同级别进行分组,根据级别的结构混合使用符号和对象。产生以下输出:[:info,:services,about:[:company,history:[:part1,:part2]]]#altsyntax[:info,:services,{:about=>[:company,{:history=>[:part1,:pa

  4. ruby-on-rails - 如何播种图像的路径? - 2

    Organization和Image具有一对一的关系。Image有一个名为filename的列,它存储文件的路径。我在Assets管道中包含这样一个文件:app/assets/other/image.jpg。播种时如何包含此文件的路径?我已经在我的种子文件中尝试过:@organization=...@organization.image.create!(filename:File.open('app/assets/other/image.jpg'))#Ialsotried:#@organization.image.create!(filename:'app/assets/other/i

  5. Ruby 和指南针路径与 yeoman 项目 - 2

    我安装了ruby​​、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom

  6. 对象的 Ruby 方法查找路径 - 2

    是否有内置的Ruby方法或众所周知的库可以返回对象的整个方法查找链?Ruby查看一系列令人困惑的类(如thisquestion中所讨论)以查找与消息对应的实例方法,如果没有类响应消息,则调用接收方的method_missing。我将以下代码放在一起,但我确信它遗漏了某些情况或者它是否100%正确。请指出任何缺陷并指导我找到一些更好的代码(如果存在)。defmethod_lookup_chain(obj,result=[obj.singleton_class])ifobj.instance_of?Classreturnadd_modules(result)ifresult.last==B

  7. ruby-on-rails - rails 中的路径解析 - 2

    我正在寻找这样解析路由路径的方法:ActionController::Routing.new("post_path").parse#=>{:controller=>"posts",:action=>"index"}应该和url_for相反更新我发现:Whatistheoppositeofurl_forinRails?Afunctionthattakesapathandgeneratestheinterpretedroute?ActionController::Routing::Routes.recognize_path("/posts")所以现在我需要将posts_path转换为“/p

  8. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  9. python3获取路径方法 - 2

    一:os.path.dirname(__file__)和os.getcwd()importospath=os.path.dirname(__file__)print("os.path.dirname(__file__)方法的结果{}".format(path))path=os.getcwd()print("os.getcwd()方法的结果{}".format(path))该脚本路径为:/User/xxx/Work1.在当前目录/User/xxx/Work运行程序结果:2.在上一级目录/User/xxx运行程序:3.在其他目录/User/xxx/Work/python运行程序:\在其他目录/Us

  10. ruby - 用 ruby​​ 将 2 个破折号插入这个字符串的最短方法是什么? - 2

    这是字符串:04046955104021109我需要这样格式化:040469551-0402-1109用ruby​​做到这一点的最短/最有效的方法是什么? 最佳答案 两个简单的插入就可以了:example_string.insert(-9,'-').insert(-5,'-')负数表示您从字符串末尾开始计数。如果您愿意,也可以从头数起:example_string.insert(9,'-').insert(14,'-') 关于ruby-用ruby​​将2个破折号插入这个字符串的最短方法是

随机推荐