【算法学习笔记】20:朴素Dijkstra与堆优化Dijkstra(无负权边单源点最短路)

2023-11-12

Dijkstra算法用于在所有边权都非负的图上,求单源点最短路

n n n是图上结点的数量, m m m是边的数量。则朴素Dijkstra算法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),适合稠密图(点少边多);堆优化版的Dijkstra算法的时间复杂度是 O ( m l o g n ) O(mlogn) O(mlogn),适合稀疏图(点多边少)。

如果是稠密图,那么边的数量 m m m和点数的平方 n 2 n^2 n2基本是一个规模的。如果用堆优化版的Dijkstra,那么复杂度就可以视为 O ( n 2 l o g n ) O(n^2logn) O(n2logn),这个时候是不如朴素版Dijkstra的。

如果是稀疏图,那么边的数量 m m m和点的数量 n n n基本是一个规模的,题目数据可能给到 1 0 5 10^5 105。如果用朴素版的Dijkstra,那么规模就是 1 0 10 10^{10} 1010了,肯定会超时。

Dijkstra算法属于贪心算法。

1 朴素Dijkstra算法

例如,要求 1 1 1号点到 n n n号点的的最短距离。

  1. d i s t [ 1 ] = 0 dist[1]=0 dist[1]=0表示到 1 1 1号点自己的距离是 0 0 0,其他的 d i s t [ i ] = + ∞ dist[i] = +\infty dist[i]=+。也就是说初始时只有起点到自己的距离是确定了的,为 0 0 0,到其它点的距离都是没有确定的。
  2. 集合 S S S中存的是当前已经确定最短路的点, S S S初始化为空集。
  3. 循环 n − 1 n-1 n1次,每次找到不在 S S S中的距离最小的点(所以第一次循环找到的是源点 1 1 1 t t t,将 t t t这个点加入到集合里(说明到 t t t的距离就已经确定了)。还要用 t t t来更新其它点,也就是说对于每个点 j j j,先到 t t t的距离 d i s t [ t ] dist[t] dist[t],加上 j j j t t t的距离 g [ j ] [ t ] g[j][t] g[j][t],如果比原来的 d i s t [ j ] dist[j] dist[j]要小,就用这个值来更新 d i s t [ j ] dist[j] dist[j]

最后,对于任意一个终点 n n n,如果 d i s t [ n ] dist[n] dist[n]不是正无穷,就说明从源点 1 1 1到目标点 n n n是可达的,最短距离就是 d i s t [ n ] dist[n] dist[n]

模板题:Dijkstra求最短路 I

在下面的实现里,因为点的标号是连续的一串属,用布尔数组st来模拟在不在集合 S S S里。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510;

// g存邻接矩阵,dist[j]存从1到j的距离
int g[N][N], dist[N];
// st[j]为true时表示j在S数组里,即已经确定距离
bool st[N];

// 计算结点1到结点n的最短距离
int dijkstra(int n) {
    // dist数组也初始化为正无穷,表示没计算过
    memset(dist, 0x3f, sizeof dist);
    // 到1自己的距离为0
    dist[1] = 0;
    // 只要循环n-1次,因为每次都会在S外找一个dist最小的
    // n-1次之后,S里已经有n-1个元素,如果有第n轮一定直接选到最后那个元素了
    // 所以没必要做到n轮,n-1轮之后dist就已经算完了
    for (int i = 0; i < n - 1; i ++ ) {
        // 在S外(st[j]为false的)找一个最小的dist[t]
        int t = -1;
        for (int j = 1; j <= n; j ++ ) {
            if (!st[j] && (t == -1 || dist[j] < dist[t ]))
                t = j;
        }
        // 将其加入到S中
        st[t] = true;
        // 用它来更新到其它点的距离,这里不需要判断只更新没确定的
        // 因为即使是已经确定的,这样求min的更新也不会破坏它
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    // 如果要求的n号点还是正无穷,那说明不可达
    if (dist[n] == 0x3f3f3f3f) return -1;
    // 否则dist[n]就是1号点到n号点的距离
    return dist[n];
}

int main() {
    // 图里每条边都初始化为正无穷,表示不可达
    memset(g, 0x3f, sizeof g);
    int n, m;
    cin >> n >> m;
    while (m -- ) {
        int x, y, z;
        cin >> x >> y >> z;
        // 由于有重边,两个结点之间只要记录最短的
        // 所以这里取min
        g[x][y] = min(g[x][y], z);
    }
    // 输出从结点1到结点n的最短距离
    cout << dijkstra(n) << endl;
    return 0;
}

注意,因为朴素Dijkstra用在稠密图上,所以直接存到邻接矩阵里。

还有一个可以优化的地方,如果只想求到 n n n的距离 d i s t [ n ] dist[n] dist[n],那么找到的t==n的时候就可以提前break出来。

2 堆优化Dijkstra算法

如果是稀疏图,题目的结点数 n n n很大,使用朴素Dijkstra算法就会超时。

稀疏图,用邻接表存储,然后用堆优化Dijkstra算法来做,下面的分析也是基于邻接表的。注意,邻接表找结点 t t t的所有边不需要遍历 n n n个点,只要遍历一下 t t t位置的链表就行了。

下面看一下在稀疏图里,Dijkstra算法还有哪些可以优化的地方。回顾一下前面朴素Dijkstra算法的过程,循环里有三个操作:

循环n - 1次
	找一个结点t,是不在S中的距离最近的点
	将t加入到S里
	用t更新其它点的距离

第二个操作:将 t t t加入到 S S S里。
就直接在数组里标记,所以是 O ( 1 ) O(1) O(1)的。一共循环了 n − 1 n - 1 n1次,所以总共的计算量规模就是 n n n

第三个操作:用 t t t更新其它点的距离。
实际上是只用到所有从 t t t出发的边,而 t t t每次都是不一样的点。所以这个操作在 n − 1 n - 1 n1次循环里实际是遍历了所有边,所以总共的计算量规模就是 m m m

第一个操作:找一个结点 t t t,是不在 S S S中的距离最近的点。
每一次都要循环 n n n次,在 n − 1 n - 1 n1次循环里,总共的计算量规模就是 n 2 n^2 n2


所以可以发现计算量的瓶颈在第一个操作,而在一堆数里面找一个最小的数,可以直接用小根堆,这样这个操作的时间复杂度可以变成 O ( 1 ) O(1) O(1)的。在 n − 1 n - 1 n1次循环里总共的计算量规模可以下降到 n n n

如果用堆来存了到每个点的距离,那么第三个操作就变成了在堆中修改一个数,在 n n n个元素的堆中修改一个元素的时间复杂度是 O ( l o g n ) O(logn) O(logn),一共要修改边数 m m m这么多次,所以这个操作的计算量规模上升到 m l o g n mlogn mlogn

所以堆优化Dijkstra算法的瓶颈在第三个操作——用 t t t更新其它点的距离。这和朴素Dijkstra算法的瓶颈是不一样的。因为这个操作,堆优化Dijkstra算法的时间复杂度就是 O ( m l o g n ) O(mlogn) O(mlogn)的。


另外, 如果手写模拟堆来做,才能支持修改堆里的任意一个元素,但是手写堆很麻烦。

如果用STL的优先队列来做,因为不支持修改任意一个元素,这里的解决方案就是用冗余。即每次都直接加入新的元素,这样做的话,堆里总共的元素个数要达到 m m m个,这样实现的堆优化Dijkstra算法的时间复杂度就变成了 m l o g m mlogm mlogm

由于 m < n 2 m < n^2 m<n2,所以 m l o g m < m l o g ( n 2 ) < 2 m l o g n mlogm < mlog(n^2) < 2mlogn mlogm<mlog(n2)<2mlogn,它和 m l o g n mlogn mlogn实际也是一个级别的。另外因为是稀疏图,所以 m m m实际远比 n 2 n^2 n2小,所以这个常数也比 2 2 2小很多了,就是一点几,这样写方便而且速度也够。


由于这样做的堆里存在很多冗余,所以找到的最小值可能之前已经确定过了,这里就用st数组来判断,如果是已经确定过的,就把它扔掉再重新找就可以了。


模板题:Dijkstra求最短路 II

在下面的实现里,因为是稀疏图所以选用邻接表来做。用邻接表来做,就不用对重边做特殊的处理了(邻接矩阵时候要取 m i n min min),因为所有重边都保存下来了,算法就保证了在重边里一定可以拿到最短的来做。

因为pair自动按第一关键字优先来比较大小,所以这里堆里存 { 距 离 , 点 编 号 } \{距离,点编号\} {,}

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 150010, M = N;

// 带权w的邻接表
int h[N], e[M], ne[M], idx, w[M];
void add_edge(int a, int b, int c) {
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

int dist[N];
bool st[N];

int dijkstra(int n) {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    // 存pair<int, int>的小根堆
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    // 到1距离为0
    heap.push({0, 1});
    
    // 这里不用n-1次这样遍历了,直接遍历到堆空
    while (heap.size()) {
        // 取出堆顶,就是最小的距离
        // 注意这里不能用auto&,因为pop之后这个top()就变了
        auto t = heap.top();
        heap.pop();
        // 但是这个距离可能已经是算过的S里的点的了
        int ver = t.second, distance = t.first;
        if (st[ver]) continue;
        // 至此,说明是S外的点,把它加到S里
        st[ver] = true;
        // 遍历ver连接的所有边,更新t连接的结点j的dist
        for (int i = h[ver]; i != -1; i = ne[i]) {
            // 这条边的结点号是j
            int j = e[i];
            // 更新
            if (dist[j] > dist[ver] + w[i]) {
                dist[j] = dist[ver] + w[i];
                // 如果更新了,还要把它加入到堆里
                // 本来应该修改,但是用STL的修改不了,直接加
                heap.push({dist[j], j});
            }
        }
    }
    // 最后一样的判断n号点距离
    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main() {
    // 清空邻接表
    memset(h, -1, sizeof h);
    int n, m;
    cin >> n >> m;
    while (m -- ) {
        int x, y, z;
        cin >> x >> y >> z;
        // 直接在邻接表里加边
        add_edge(x, y, z);
    }
    cout << dijkstra(n) << endl;
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【算法学习笔记】20:朴素Dijkstra与堆优化Dijkstra(无负权边单源点最短路) 的相关文章

  • Dijkstra算法——java实现

    面试时遇到Dijkstra算法 这个算法我是知道的 但是没具体写过 所以答题比较慢 抽时间实现了下这个算法 nbsp Dijkstra算法基本思路 该算法的基本思路是这样的 从起始点开始 将未访问过的相邻节点加入一个优先队列 类似于广度优先
  • TT 的旅行日记(Dijkstra)

    问题描述 xff1a 众所周知 xff0c TT 有一只魔法猫 今天他在 B 站上开启了一次旅行直播 xff0c 记录他与魔法猫在喵星旅游时的奇遇 TT 从家里出发 xff0c 准备乘坐猫猫快线前往喵星机场 猫猫快线分为经济线和商业线两种
  • 2、无人驾驶--路径规划算法:Dijkstra

    目录 2 Dijkstra2 1 算法简介2 2 算法思路具体流程 xff1a 2 3 算法具体实现2 3 1 程序详解 2 Dijkstra 声明 xff1a 本文是学习古月居 基于栅格地图的机器人路径规划算法指南 黎万洪 后写的笔记 x
  • 游戏中的寻路算法--Dijkstra算法和AStar(A*)算法

    前言 如今游戏中最最常用的两种寻路算法为Dijkstra算法和A 算法 xff0c 虽然现代引擎中的Al寻路算法看似很复杂 其实大部分是Dijkstra算法或者A 算法的变种 导航网格 图数据 无论是2D游戏的导航网格或者3D游戏导航网格
  • Dijkstra算法-(迪杰斯特拉)算法的迭代实现与优先队列实现 图解算法过程

    Dijkstra算法 迪杰斯特拉 算法之迭代实现 Dijkstra算法 迪杰斯特拉 算法之优先队列实现 该算法的核心原理 很简单 如下图所示 先说说Dijkstra算法 迪杰斯特拉 算法之迭代实现 如下图为详细步骤 代码如下 两种实现方法都
  • 深入分析 (迪杰斯特拉算法) Dijkstra 算法实现原理

    迪杰斯特拉 Dijkstra 算法是典型最短路径算法 用于计算一个节点到其他节点的最短路径 它的主要特点是以起始点为中心向外层层扩展 广度优先搜索思想 直到扩展到终点为止 基本思想 通过Dijkstra计算图G中的最短路径时 需要指定起点s
  • 【复赛模拟试题】收费站(二分答案+Dijkstra)

    问题描述 在某个遥远的国家里 有n个城市 编号为1 2 3 n 这个国家的政府修建了m条双向的公路 每条公路连接着两个城市 沿着某条公路 开车从一个城市到另一个城市 需要花费一定的汽油 开车每经过一个城市 都会被收取一定的费用 包括起点和终
  • 【路径规划】(1) Dijkstra 算法求解最短路,附python完整代码

    好久不见 我又回来了 这段时间把路径规划的一系列算法整理一下 感兴趣的点个关注 今天介绍一下机器人路径规划算法中最基础的 Dijkstra 算法 文末有 python 完整代码 那我们开始吧 1 算法介绍 1959 年 荷兰计算机科学家 E
  • 天梯题集——紧急救援(Dijkstra+倒序打印分析)

    Dijkstra算法 用于求单源到其他点的最短路径 紧急救援 该题与 Dijkstra模板题 的不同之处在于该题需要记录更多信息 主要思路从局部最优到整体最优 类似dp的思想 include
  • Dijkstra算法:如果有两个或多个权重最小的节点怎么办?

    在 Dijkstra 算法中 如果算法中的某个点有两个或多个权重最小的节点 我该怎么办 在维基百科中 http en wikipedia org wiki Dijkstra 27s algorithm在步骤号 6 它说 将暂定距离最小的未访
  • 带优先级队列的 Dijkstra 算法

    在我的 Dijkstra 算法的实现中 我有 1 个包含所有节点的数组和 1 个包含所有节点的优先级队列 每当一个节点出队时 我都会用新的距离和它的来源更新所有相邻节点 这样我就可以回溯路径 优先级队列中的节点将更新为新距离 数组中的节点将
  • 如何使用 A-Star 或 Dijkstra 算法解决 15 个难题?

    我在一本人工智能书籍中读到 用于模拟或游戏中寻路的流行算法 A Star Dijkstra 也被用来解决著名的 15 谜题 谁能给我一些关于如何将 15 个拼图简化为节点和边图的指示 以便我可以应用其中一种算法 如果我将图中的每个节点视为游
  • Dijkstra 算法适用于有向图还是无向图?

    我一直试图用谷歌搜索这个 但我发现的结果只会增加我的困惑 好像两者都可以用 如果是这样 它的默认设计目的是什么 需要进行哪些更改才能使其以非默认方式工作 无论是定向还是非定向 编辑 仅供参考 上学期我遇到了一个问题 我得到了这样的列表 机场
  • 当这个常规队列版本也是正确的时候,为什么 Dijkstra 算法需要优先级队列?

    我已阅读以下内容 但请查看下面我的代码 为什么Dijkstra算法使用堆 优先级队列 https stackoverflow com questions 12481256 why dijkstras algorithm uses heap
  • 具有负权重的 Dijkstra 算法

    我们可以使用具有负权重的 Dijkstra 算法吗 STOP 在你认为 哈哈 你可以在两点之间无休止地跳跃并获得一条无限便宜的路径 之前 我更倾向于考虑单向路径 其应用是具有点的山区地形 显然 从高到低并不需要能量 事实上 它会产生能量 因
  • 实施 Dijkstra 算法

    我的任务是 大学课程 实施某种形式的寻路 现在 在规范中 我可以实现强力 因为要搜索的节点数量有限制 开始 中间两个 结束 但我想重新使用此代码并来实现迪杰斯特拉算法 http en wikipedia org wiki Dijkstra
  • 原始地理坐标和图形节点之间的最短路径

    我已经实现了一个简单的 Dijkstra 算法 用于使用 Java 查找 osm 地图上的最短路径 从 osm 文件创建的图形中的寻路效果非常好 但是 如果用户的当前位置和 或目的地不是该图的节点 只是原始坐标 我们如何将这些坐标 链接 到
  • Dijkstra算法的时间复杂度是多少

    Dijkstra V E S O 1 for each vertex v V O V d v O 1 d source 0 O 1 while S V O V v non visited vertex with the smallest d
  • java有索引的最小优先级队列吗?

    我需要它来实现 Dijkstra 算法 并且我确实有自己的实现 但是使用 java 自己的类记录我的代码会更容易 不 Java标准库没有这样的数据结构 我想大多数人都用这个 http algs4 cs princeton edu 24pq
  • Dijkstra 谈“软件工程”[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 埃兹格 迪杰斯特拉 Edsger D

随机推荐

  • C语言 IDE的介绍及安装

    目录 C语言 IDE介绍 GCC C Free Code Blocks CLang CLion XCode Dev C Turbo C Visual C 6 0 Visual Studio C语言 IDE安装 安装包 版本选择 注意事项 C
  • 使用VMware给Ubuntu增加磁盘容量

    一般只给虚拟机里面的Ubuntu很少的空间 到了空间不足就尴尬了 那么就扩充磁盘解决问题吧 一 VMware手动扩容 打开虚拟机 选择你要扩充的客户机 点击 编辑虚拟机设置 然后详细设置 选择硬盘 gt 扩展 gt 目标总磁盘大小 不是增量
  • [力扣c++实现] 152. 乘积最大子数组

    152 乘积最大子数组 给你一个整数数组 nums 请你找出数组中乘积最大的非空连续子数组 该子数组中至少包含一个数字 并返回该子数组所对应的乘积 测试用例的答案是一个 32 位 整数 子数组 是数组的连续子序列 示例 1 输入 nums
  • xshell无法连接虚拟机原因Connection failed. Type `help' to learn how to use Xshell prompt.

    问题分析 xshell无法连接的问题有多种 比如虚拟机没有安装ssh服务 虚拟机没有启动ssh服务 又或者是防火墙 禁用端口等问题 这些都比较容易解决 下面我介绍的是我遇到的一种比较难的无法连接情况 VMware无法在Windows下创建适
  • Proxy(代理)服务器

    代理服务器 代理服务器必须有DNS地址 如果开启转发需要在客户端设置DNS地址 NAT 是直接与目标服务器通信的 也就是直接访问的baidu服务器 目标地址是baidu服务器的地址 所以必须要有DNS来解析主机名 如果是通过代理客户端是没有
  • 全球及中国生物技术产业创新发展模式及十四五应用方向研究报告2021-2027年

    全球及中国生物技术产业创新发展模式及十四五应用方向研究报告2021 2027年HS HS HS HS HS HS HS HS HS HS HS HS HS HS HS HS HS HS 修订日期 2021年10月 搜索鸿晟信合研究院查看官网
  • 交叉编译器简介以及ARM交叉编译器arm-linux-gcc

    一 交叉编译器简介 在一种计算机环境中运行的编译程序 能编译出在另外一种环境下运行的代码 这个编译过程就叫交叉编译 简单地说 就是在一个平台上生成另一个平台上的可执行代码 二 体系结构与操作系统 1 常见的体系结构有ARM结构 x86结构等
  • SPSS异常值处理编程

    SPSS异常值处理编程 异常值是指在数据集中与其他观测值相比明显不同的值 在数据分析过程中 异常值可能会对结果产生不良影响 因此需要进行异常值处理 SPSS作为一种广泛使用的数据分析工具 提供了多种方法来检测和处理异常值 本文将介绍SPSS
  • 【python】UI自动化——鼠标悬浮显示二级菜单相关操作

    一 来个百度示例吧 想要点击如下图的图片 先上代码 import time from selenium import webdriver from selenium webdriver import ActionChains def sta
  • matlab 数据降维和重构_处理不平衡机器学习数据时需要了解的技术

    我们在处理真实世界机器学习数据集时遇到的主要挑战之一是数据的比例不平衡 欺诈检测是这类数据的最好例子 在本文中 我们将使用kaggle中的信用卡欺诈检测数据集 www kaggle com mlg ulb creditcardfraud 在
  • 推荐基于VUE2.0自定义分页插件

    基于vue2 0实现的自定义分页 可设置每页显示条数 带跳转框直接跳转到相应页面 文档地址 https gitee com it distant branch vue custom pages 实现效果如下 支持功能 x 自定义分页条数 x
  • -bash: ./startup.sh: Permission denied

    今天在执行tomcat的时候 用户没有权限 而导致无法执行 用命令chmod 修改一下bin目录下的 sh权限就可以了 如chmod u x sh
  • YUV420P与YUVJ420P

    1 YUV420P与YUVJ420P AV PIX FMT YUV420P lt planar YUV 4 2 0 12bpp 1 Cr Cb sample per 2x2 Y samples AV PIX FMT YUVJ420P lt
  • TOF 3DSensor SDK下载

    DCAM710 与 DCAM100 SDK下载 https picozense picovr com cn sdk html PicoZense SDK是基于PicoZense深度摄像头的软件开发包 支持Windows Linux Open
  • Mybatis Cannot convert string to java.sql.Timestamp value;

    生成默认无参构造函数
  • SpringAop使用的到底是JDK动态代理还是Cglib?

    1 从源码分析 optimize标志已设置 也就是为true 设置proxyTargetClass 目标代理类 标志 更改proxyTargetClass 目标代理类 标志的方法 没有指定代理接口 2 错误的推论 3 最终的推论 什么时候使
  • CodeGeeX中这些隐藏的设置,你知道吗?

    随着CodeGeeX整体性能的升级 越来越多的用户发现CodeGeeX的很多实用功能 能够帮助程序员更快更好的编写代码和解决技术问题 近期 我们看到许多用户在使用CodeGeeX的过程中 有一些相似的疑问 比如 很多人希望能够通过调整设置
  • 以太网流量控制——PAUSE帧

    http www tuicool com articles Bzu2uuf 今天在测试DPDK性能的时候 发现发包工具的发包速率无法提升上去 千兆网卡设置速率70W qps 只能发出1W的速率 抓包发现有大量的PAUSE流控帧 一 PAUS
  • High-resolution Face Swapping via Latent Semantics Disentanglement

    High resolution Face Swapping via Latent Semantics Disentanglement 人脸视频交换 从浅层派生结构属性 从深层派生外观属性 结构转移潜在方向 进一步分离结构属性中的身份和姿态信
  • 【算法学习笔记】20:朴素Dijkstra与堆优化Dijkstra(无负权边单源点最短路)

    Dijkstra算法用于在所有边权都非负的图上 求单源点最短路 设 n n n是图上结点的数量 m m m是边的数量 则朴素Dijkstra算法的时间复杂度是 O