• 图论(四)—最短路问题(Dijkstra)


    一、最短路

            概念:从某个点 A 到另一个点B的最短距离(或路径)。从点 A 到 B 可能有多条路线,多种距离,求其中最短的距离和相应路径。

            最短路径分类:

            单源最短路:图中的一个点到其余各点的最短路径

            多源最短路:图中任意两点的最短路径

             框架图解:

    二、朴素Dijkstra算法

            算法思想(仅限于非负权重值):从起始点开始,使用贪心的策略,通过加点的方法,每次遍历到起始点距离最近且未被访问过的邻接节点 t ,将 t 加入到集合 S 中,直到访问过所有节点。

         通过 N 次循环确定 n 个点到起点的最短路距离 

            时间复杂度为 O(n^{2})

            1.在没有确定最短路中的所有点(集合 S 以外)找出距离起点最近的点 t   

            2.对 t 进行标记,加入到集合中

            3.用 t 更新其他点的最短路距离

           集合 S :已经确定最短路的点(被访问过的点)

            定义数组 dis:从起始点到某点 ( 3 号节点 ) 的最短距离( dis[3] )

            定义二维数组add: 

                    add[u][v] 表示从 节点 u 到 节点 v 的距离(区分单向与双向,双向则add[v][u]=add[u][v]

          初始化: dis[1]=0 \, \, \, \, \, \, \, \, \, dis[x]=+\bowtie \, \, \, \, \, \, \, \, \, 2\leq x\leq n (以节点 1 为起始点)

            若 节点 u 与 节点 v 之间没有路径,初始化为 add[u][v]=+\Join

          核心代码:

    1. for(int i=1;i<=n;i++)
    2. {
    3. int t=-1;
    4. for(int j=1;j<=n;j++) // 在没有确定最短路中的所有点找出距离最短的那个点 t
    5. if(!s[j] && (t==-1||dis[t]>dis[j]))
    6. t=j;
    7. s[t]=true; // 代表 t 这个点已经确定最短路了
    8. for(int j=1;j<=n;j++) // 用 t 更新其他点的最短距离
    9. dis[j] = min(dis[j],dis[t]+add[t][j]);
    10. }

    样例解释:对于下图,求出节点 A 的单源最短路 

     

    n12345
    dis07395

    三、堆优化dijkstra算法

            在朴素dijkstra算法中,遍历点是通过for循环对所有节点判断一遍得出的,”对所有节点判断“这一操作消耗了更多的时间。

            算法思想:

            可以通过堆(优先队列)进行优化,堆(优先队列)存储节点起始点到该点最短距离,堆(优先队列)按照距离自动排序取距离最小且未被访问过的点,同通过用邻接链表(或邻接表)储存图的方法,再进行松弛操作,并将进行松弛操作的节点插入堆中。

             ①.初始化距离:数组dis 都初始化为 0x3f3f3f3f(无穷大),并将 1 号节点插入堆中 (dis[1]=0)

             ②取出堆顶的点(当前起始点到该点距离最小),判断是否被访问过,不断弹出取堆顶,直至找到未被访问的节点,再根据邻接链表(或邻接表)拓展。

             ③进行松弛操作,把松弛的点和距离插入到堆中。

            堆优化代码:

    1. void dij(int s)
    2. {
    3. priority_queue< pair<int,int> > q; // 利用优先队列
    4. q.push(make_pair(0,s));
    5. memset(dis,127,sizeof(dis));
    6. dis[s]=0;
    7. while(q.size())
    8. {
    9. int u=q.top().second;
    10. q.pop();
    11. if(vis[u]==1) continue;
    12. vis[u]=1;
    13. for(int i=head[u];i;i=edge[i].next) // 链式前向星
    14. {
    15. int v=edge[i].to;
    16. int w=edge[i].w;
    17. if(dis[v]>dis[u]+w)
    18. {
    19. dis[v]=dis[u]+w;
    20. q.push(make_pair(-dis[v],v)); // 将路径以负数保存,优先队列默认大根堆
    21. }
    22. }
    23. }
    24. }

    关于dijkstra算法的正确性证明,参考博文:

    Dijkstra贪心算法的准确性证明_为什么这种方法求下来的路径一定是最短?试分析一下它的正确性-CSDN博客

    四、dijkstra算法不能用于有负权边的图

            通过上述dijkstra思想可以得出,每次松弛操作就是通过当前离起始点最近的点来更新其他点的距离,下面举例说明。

    当此时通过 节点 4 更新其他节点, dijkstra 思想已经确定 dis [ 4 ] 为 起始点 到 节点 4 的最短路,显然错误。

  • 相关阅读:
    发明专利和实用新型专利的区别
    鸿蒙开发(五)鸿蒙UI开发概览
    怎么做手机App测试?app测试详细流程和方法介绍(即学即用宝典)
    SpringBoot读取配置的方式
    SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)
    VMware ESXi学习笔记
    Git 详细教程之三: Git 分支操作
    PaddleOCR模型移植安卓
    代码随想录算法训练营Day45 | 动态规划(7/17) LeetCode 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数
    如何给开发板设置静态IP
  • 原文地址:https://blog.csdn.net/Zhang_Qin123/article/details/139046470