代码随想录训练营 Day58打卡 图论part08 拓扑排序 dijkstra朴素版 + 堆优化版

news2024/12/24 8:44:22

代码随想录训练营 Day58打卡 图论part08

一、拓扑排序

例题:卡码117. 软件构建

题目描述
某个大型软件项目的构建系统拥有 N 个文件,文件编号从 0 到 N - 1,在这些文件中,某些文件依赖于其他文件的内容,这意味着如果文件 A 依赖于文件 B,则必须在处理文件 A 之前处理文件 B (0 <= A, B <= N - 1)。请编写一个算法,用于确定文件处理的顺序。
输入描述
第一行输入两个正整数 N, M。表示 N 个文件之间拥有 M 条依赖关系。
后续 M 行,每行两个正整数 S 和 T,表示 T 文件依赖于 S 文件。
输出描述
输出共一行,如果能处理成功,则输出文件顺序,用空格隔开。
如果不能成功处理(相互依赖),则输出 -1。
输入示例
5 4
0 1
0 2
1 3
2 4
输出示例
0 1 2 3 4
提示信息
文件依赖关系如下:

所以,文件处理的顺序除了示例中的顺序,还存在
0 2 4 1 3
0 2 1 3 4
等等合法的顺序。

该代码的目标是通过拓扑排序来解决有向图中的依赖关系问题。具体来说,给定 n 个文件以及它们的依赖关系(即 m 条边),要求我们找出一种拓扑顺序,使得每个文件都能在它所依赖的文件之后被执行。如果无法找到合法的拓扑排序(即存在环),则输出 -1。

拓扑排序的定义:
拓扑排序是指对一个有向无环图(DAG)中的节点进行排序,使得对于每一条有向边 (u, v),节点 u 出现在节点 v 之前。换句话说,拓扑排序可以用于确定某些任务之间的依赖顺序。

算法思路

  1. 入度表:首先,为每个节点(文件)构建一个入度表,表示每个节点有多少条边指向它。即,如果一个文件有 k 个依赖的文件,那么它的入度为 k。

  2. 依赖关系图:使用邻接表(umap)来记录每个文件所依赖的其他文件。每个节点 s 指向其依赖的文件 t。

  3. 初始化队列:将所有入度为 0 的节点(即没有依赖的文件)加入队列中,这些节点可以作为排序的起始点。

  4. BFS 过程:

    (1)从队列中逐一取出节点,将其加入结果列表。
    (2)对每个节点 cur 的依赖节点(指向的文件)进行处理,更新它们的入度表(减少它们的入度)。
    (3)如果某个节点的入度变为 0,表示它的所有依赖文件已经处理完毕,可以加入队列进行处理。

  5. 检测环路:如果拓扑排序完成后,结果中包含的节点数不等于总节点数,说明存在环路,无法完成排序,此时返回 -1。

代码实现

from collections import deque, defaultdict

def topological_sort(n, edges):
    inDegree = [0] * n  # inDegree 数组记录每个节点的入度,初始为0
    umap = defaultdict(list)  # 邻接表,记录节点之间的依赖关系,默认为空列表

    # 构建图和入度表
    for s, t in edges:
        inDegree[t] += 1  # 每当一个节点被指向,其入度增加
        umap[s].append(t)  # s 指向 t,添加到邻接表中

    # 初始化队列,将所有入度为0的节点加入队列
    queue = deque([i for i in range(n) if inDegree[i] == 0])
    result = []  # 结果数组,用于存储拓扑排序的结果

    # 开始广度优先搜索(BFS)
    while queue:
        cur = queue.popleft()  # 当前队列中的节点,已无任何依赖
        result.append(cur)  # 将其加入结果列表中
        for file in umap[cur]:  # 遍历该节点指向的所有节点
            inDegree[file] -= 1  # 当前节点的出度边被移除,其指向节点的入度减1
            if inDegree[file] == 0:  # 如果入度变为0,说明该节点可以被处理
                queue.append(file)  # 将其加入队列

    # 如果拓扑排序中包含所有节点,说明排序成功;否则,说明有环
    if len(result) == n:
        print(" ".join(map(str, result)))  # 打印排序结果
    else:
        print(-1)  # 如果结果不包含所有节点,输出-1,表示图中有环

# 主程序,读取输入
if __name__ == "__main__":
    # 读取节点数量 n 和边的数量 m
    n, m = map(int, input().split())
    # 读取 m 条边,每条边由两个整数 s 和 t 表示
    edges = [tuple(map(int, input().split())) for _ in range(m)]
    # 调用拓扑排序函数
    topological_sort(n, edges)

卡码题目链接
题目文章讲解

二、dijkstra(朴素版)

例题:卡码47. 参加科学大会

题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。
小明的起点是第一个车站,终点是最后一个车站。然而,途中的各个车站之间的道路状况、交通拥堵程度以及可能的自然因素(如天气变化)等不同,这些因素都会影响每条路径的通行时间。
小明希望能选择一条花费时间最少的路线,以确保他能够尽快到达目的地。
输入描述
第一行包含两个正整数,第一个正整数 N 表示一共有 N 个公共汽车站,第二个正整数 M 表示有 M 条公路。
接下来为 M 行,每行包括三个整数,S、E 和 V,代表了从 S 车站可以单向直达 E 车站,并且需要花费 V 单位的时间。
输出描述
输出一个整数,代表小明从起点到终点所花费的最小时间。
输入示例
7 9
1 2 1
1 3 4
2 3 2
2 4 5
3 4 2
4 5 3
2 6 4
5 7 4
6 7 9
输出示例
12
提示信息
能够到达的情况:
如下图所示,起始车站为 1 号车站,终点车站为 7 号车站,绿色路线为最短的路线,路线总长度为 12,则输出 12。

不能到达的情况:
如下图所示,当从起始车站不能到达终点车站时,则输出 -1。

Dijkstra算法的基本步骤:

  1. 初始化:

    设定源点到自己的距离为0,其他节点的距离为无穷大。
    使用一个布尔数组 visited 来记录哪些节点已经被处理过,防止重复处理。

  2. 选择未访问的节点中距离源点最近的节点:
    遍历所有未访问的节点,选择距离源点最近的节点 cur。

  3. 更新与该节点相连的未访问节点的最短距离:
    对于当前节点 cur,检查与其相连的所有节点,更新它们到源点的距离。如果通过 cur 到达某个未访问节点的距离小于已知最短距离,则更新该节点的最短距离。

  4. 重复选择节点,直到所有节点都被访问过,或者没有可到达的未访问节点。

实现细节:

  • 邻接矩阵:我们使用一个 grid 矩阵来表示图,grid[i][j] 表示从节点 i 到节点 j 的边权重。初始化时,所有边的权重为无穷大float(‘inf’),表示没有连接。
  • 距离数组:minDist 用于记录源点到每个节点的最短距离。
  • 访问标记数组:visited 用于记录哪些节点已经处理完毕。

版本一 dijkstra(朴素版)

import sys

def dijkstra(n, m, edges, start, end):
    # 初始化邻接矩阵 grid,大小为 (n+1) x (n+1),初始值为无穷大,表示没有连接的边
    grid = [[float('inf')] * (n + 1) for _ in range(n + 1)]
    
    # 填充邻接矩阵,将每条边的权重记录到矩阵中
    for p1, p2, val in edges:
        grid[p1][p2] = val  # 从节点 p1 到节点 p2 的边权重为 val

    # 初始化最短距离数组 minDist,所有节点的最短距离初始为无穷大
    minDist = [float('inf')] * (n + 1)
    
    # 初始化访问数组 visited,记录每个节点是否已经被访问
    visited = [False] * (n + 1)

    # 起点的最短距离为0(起点到起点的距离为0)
    minDist[start] = 0

    # 进行 n 次遍历,每次选择一个节点加入到已处理的集合中
    for _ in range(1, n + 1):  # 遍历所有节点
        minVal = float('inf')  # 当前最小的距离
        cur = -1  # 当前选择的节点

        # 遍历所有节点,选择未访问过的且距离源点最近的节点
        for v in range(1, n + 1):
            if not visited[v] and minDist[v] < minVal:
                minVal = minDist[v]
                cur = v

        if cur == -1:  # 如果没有找到未访问的节点,提前结束
            break

        # 将当前选中的节点标记为已访问
        visited[cur] = True

        # 更新与当前节点相邻的未访问节点的距离
        for v in range(1, n + 1):
            # 如果 v 未访问且有从 cur 到 v 的边,并且通过 cur 到 v 的距离比现有的距离更短
            if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]:
                # 更新节点 v 的最短距离
                minDist[v] = minDist[cur] + grid[cur][v]

    # 返回从起点 start 到终点 end 的最短距离
    # 如果终点的距离仍为无穷大,表示不可达,返回 -1
    return -1 if minDist[end] == float('inf') else minDist[end]

# 主程序,处理输入并调用 Dijkstra 算法
if __name__ == "__main__":
    input = sys.stdin.read  # 读取输入
    data = input().split()  # 按空格分割输入数据
    n, m = int(data[0]), int(data[1])  # n 表示节点数,m 表示边数

    # 读取 m 条边,每条边由起点 p1、终点 p2 和边权重 val 组成
    edges = []
    index = 2
    for _ in range(m):
        p1 = int(data[index])
        p2 = int(data[index + 1])
        val = int(data[index + 2])
        edges.append((p1, p2, val))  # 将边加入到 edges 列表中
        index += 3

    # 起点为 1,终点为 n
    start = 1  # 设置起点
    end = n    # 设置终点

    # 调用 Dijkstra 算法计算从起点到终点的最短路径
    result = dijkstra(n, m, edges, start, end)

    # 输出最短路径的距离
    print(result)

版本二 dijkstra(堆优化版)

堆优化的 Dijkstra 算法 相对于传统的 Dijkstra 算法,是通过使用 小顶堆 来加速寻找最短路径的节点。我们不再通过遍历所有节点来选择未访问且最近的节点,而是通过小顶堆高效地获取当前最小的路径节点。这种优化能显著减少计算量,尤其是在边稀疏的图中。

三部曲的改进:

  1. 选取最近的节点:我们通过一个小顶堆来存储每个节点到源点的最短路径,并每次从堆顶取出路径最短的节点进行处理。
  2. 标记为已访问:从堆中取出的节点为当前未访问的节点中距离最小的,标记该节点为已访问。
  3. 更新其他节点的最短距离:遍历该节点的所有相邻节点,更新它们到源点的最短距离,并将更新后的距离加入堆中。
import heapq  # 导入heapq库,用于实现小顶堆

# 定义 Edge 类,表示一条边,包含目标节点和边的权值
class Edge:
    def __init__(self, to, val):
        self.to = to  # 边的目标节点
        self.val = val  # 边的权值

# 堆优化的 Dijkstra 算法实现
def dijkstra(n, m, edges, start, end):
    # 初始化邻接表,使用列表存储与每个节点相连的边
    grid = [[] for _ in range(n + 1)]  # n + 1,因为节点编号从 1 开始

    # 构建邻接表,将每条边添加到对应的节点列表中
    for p1, p2, val in edges:
        grid[p1].append(Edge(p2, val))  # 记录 p1 到 p2 的边,权值为 val

    # 初始化最短距离数组,所有节点的初始最短距离为无穷大
    minDist = [float('inf')] * (n + 1)
    
    # 初始化访问数组,记录节点是否已被访问
    visited = [False] * (n + 1)

    # 小顶堆,堆中存储 (距离, 节点) 元组
    pq = []
    
    # 将起点加入堆中,起点到自身的距离为 0
    heapq.heappush(pq, (0, start))
    minDist[start] = 0  # 起点到自身的距离为 0

    # 处理堆中的节点
    while pq:
        cur_dist, cur_node = heapq.heappop(pq)  # 从堆中取出距离最近的节点
        
        if visited[cur_node]:  # 如果该节点已经访问过,跳过
            continue

        visited[cur_node] = True  # 标记该节点为已访问

        # 遍历当前节点的所有邻接边
        for edge in grid[cur_node]:
            # 如果目标节点未访问过,且通过当前节点的路径更短,则更新该路径
            if not visited[edge.to] and cur_dist + edge.val < minDist[edge.to]:
                # 更新目标节点的最短距离
                minDist[edge.to] = cur_dist + edge.val
                # 将更新后的 (最短距离, 目标节点) 加入堆中
                heapq.heappush(pq, (minDist[edge.to], edge.to))

    # 如果终点的最短距离仍为无穷大,说明不可达,返回 -1;否则返回最短距离
    return -1 if minDist[end] == float('inf') else minDist[end]

# 读取输入数据并调用 Dijkstra 算法
if __name__ == "__main__":
    # 输入节点数 n,边数 m
    n, m = map(int, input().split())
    
    # 输入每条边的信息,边的格式为 (起点, 终点, 权值)
    edges = [tuple(map(int, input().split())) for _ in range(m)]
    
    # 设置起点和终点
    start = 1  # 默认起点为 1
    end = n    # 默认终点为 n

    # 调用 Dijkstra 算法计算从起点到终点的最短路径
    result = dijkstra(n, m, edges, start, end)
    
    # 输出最短路径结果
    print(result)

详细说明:

  1. 邻接表的构建:
    grid = [[] for _ in range(n + 1)]:初始化一个邻接表,grid[i] 表示节点 i 的所有相邻节点及其边的权重。
    遍历输入的边,构建邻接表,将每条边加入到对应的起点节点的列表中。

  2. 小顶堆的使用:
    使用 Python 的 heapq 模块实现小顶堆。堆中存储 (距离, 节点) 元组,每次取出距离最小的节点。
    heapq.heappush(pq, (0, start)):将起点的距离(0)和起点节点加入堆中。
    heapq.heappop(pq):从堆中取出距离最小的节点,并对其进行处理。

  3. 更新最短路径:
    对于当前取出的节点 cur_node,遍历它所有的邻接节点,计算通过 cur_node 到达邻接节点的距离是否更短。如果更短则更新最短距离,并将其加入堆中。

  4. 提前退出:
    如果当前从堆中取出的节点已经访问过,直接跳过,这样可以避免重复处理。

  5. 返回结果:
    如果终点节点 end 的最短距离为无穷大,说明不可达,返回 -1;否则返回从起点到终点的最短距离。

堆优化的思路:

  • 使用小顶堆时,所有边的处理顺序已经由堆的结构自动排序成了从最短到最长的顺序。因此不需要每次遍历所有未访问节点来查找最短路径,这样可以显著提高算法效率。
  • 通过小顶堆优化后,Dijkstra 算法的时间复杂度从 O(n^2) 降低到 O(E log V),其中 E 是边的数量,V是节点的数量。这在处理稀疏图时效率非常高。

卡码题目链接
题目文章讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2137197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不同的二叉搜索树

题目 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff…

JSP经典设计模式流程分析:JSP+JavaBean设计模式+MVC设计模式

JSP两种经典设计模式 Model1设计模式:JSPJavaBean 架构图 什么是JavaBean JavaBean是一种JAVA语言写成的可重用组件&#xff0c;它遵循特定的编程规范&#xff0c;如类必须是公共的、具有无参构造函数&#xff0c;并提供getter/setter方法等。这里的JavaBean不单单指的是实体…

五、回溯算法-算法总结

文章目录 五、回溯算法5.1 背景5.2 模板5.3 集合类5.3.1 子集5.3.2 子集2 5.4 排列类5.4.1 全排列5.4.2 全排列2 5.5 组合类5.5.1 组合总和5.5.2 电话号码的字母组合 五、回溯算法 5.1 背景 回溯法&#xff08;backtrack&#xff09;常用于遍历列表所有子集&#xff0c;是 DF…

OpenAI草莓正式发布,命名o1

一、相关介绍 当地时间 9 月 12 日&#xff0c;OpenAI 推出全新模型 o1&#xff0c;它是该公司计划推出的一系列“推理”模型中的首个&#xff0c;也就是此前业内传闻许久的“Strawberry&#xff08;草莓&#xff09;”项目。 据悉&#xff0c;o1 模型在众多任务中能够比人类更…

LabVIEW机动车动态制动性能校准系统

机动车动态制动性能测试系统通过高精度的硬件设备与LabVIEW软件的紧密配合&#xff0c;实现了对机动车制动性能的精确校准与评估。系统不仅提高了测试的精确性和效率&#xff0c;而且具备良好的用户交互界面&#xff0c;使得操作更加简便、直观。 项目背景 随着机动车辆数量的…

C++:类和对象全解

C&#xff1a;类和对象全解 一、类的定义和初始化&#xff08;一&#xff09;类的定义1、类的成员变量&#xff08;1&#xff09;成员变量&#xff08;2&#xff09;成员函数 2、实例化对象&#xff08;1&#xff09;采用普通构造函数&#xff08;2&#xff09;采用初始化列表 …

MySQL数据的增删改查(二)

目录 约束 非空约束&#xff08;NOT NULL&#xff09; 唯一约束&#xff08;UNIQUE&#xff09; 默认值约束&#xff08;DEFAULT&#xff09; 主键约束&#xff08;PRIMARY KEY&#xff09; 外键约束&#xff08;FOREIGN KEY&#xff09; 检查约束&#xff08;CHECK&…

LabVIEW机械产品几何精度质检系统

随着制造业的发展&#xff0c;对产品质量的要求越来越高&#xff0c;机械产品的几何精度成为衡量其品质的重要指标。为了提高检测效率和精度&#xff0c;开发了一套基于LabVIEW的几何精度质检系统&#xff0c;该系统不仅可以自动化地进行几何尺寸的测量&#xff0c;而且能实时分…

kafka 之 本地部署单机版

安装JDK 查看你选择的版本需要安装哪一个版本的jdk 网址 下载 JDK下载 注&#xff1a;如果网页不允许下载&#xff0c;使用wget命令下载即可&#xff0c;下载之后安装。 建议使用rpm安装&#xff0c;之后使用 update-alternatives --config java 控制当前环境使用Java的版…

SpringBoot 处理 @KafkaListener 消息

消息监听容器 1、KafkaMessageListenerContainer 由spring提供用于监听以及拉取消息&#xff0c;并将这些消息按指定格式转换后交给由KafkaListener注解的方法处理&#xff0c;相当于一个消费者&#xff1b; 看看其整体代码结构&#xff1a; 可以发现其入口方法为doStart(),…

2024-2025年最全的计算机软件毕业设计选题大全

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

通过API接口获取数据:高效、灵活的数据交互之道

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。企业和开发者对于数据的获取、处理和分析的需求日益增长。API&#xff08;应用程序编程接口&#xff09;接口作为连接不同系统和应用程序的桥梁&#xff0c;提供了一种高效、灵活的方式来获取和交换数据。本文将探讨为…

rust + bevy 实现小游戏 打包成wasm放在浏览器环境运行

游戏界面 代码地址 github WASM运行 rustup target install wasm32-unknown-unknown cargo install wasm-server-runner cargo run --target wasm32-unknown-unknowncargo install wasm-bindgen-cli cargo build --release --target wasm32-unknown-unknown wasm-bindgen --…

工厂模式(二):工厂方法模式

一、概念 工厂方法模式&#xff08;Factory Method&#xff09;&#xff0c;定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。从而使得系统更加灵活。客户端可以通过调用工厂方法来创建所需的产品&#xff0c;而不必…

Linux进程间通信——管道实现实战;深度学习,探索管道接口、特性、情况

前言&#xff1a;本节内容仍是管道&#xff0c; 上节内容我们学习了管道的原理。 这节内容将在原理的基础上&#xff0c; 讲解管道的编程&#xff0c; 特性&#xff0c;应用等等。 下面开始我们的学习吧。 ps&#xff1a;本节内容需要了解一些管道的原理&#xff0c; 希望友友们…

AIGC-初体验

线性分类 提问&#xff0c;目的试图让AI自动线性分类 A类&#xff1a;(10,21),&#xff08;3,7&#xff09;,(9,20&#xff09;(121,242) B类&#xff1a;(3,9),(5,11),(70,212),(11,34) 根据线性关系分类 请问 (100,300)&#xff0c;&#xff08;100&#xff0c;201&#xff…

nacos和eureka的区别详细讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; Nacos 和 Eureka 是两种服务注册与发现的组件&#xff0c;它们在微服务架构中扮演重要角色。两者虽然都是为了解决服务发现的问题&#xff0c;但在功能特性、架构、设计理念等方面有很多不同。以下是详细的…

【期末复习】软件项目管理

前言&#xff1a; 关于软件项目管理这一科目的重要期末考点&#xff0c;希望对你有帮助。 目录 质量管理可能遇到的问题 软件项目质量管理 软件项目风险管理 进度 题1 题2 题3 成本 题1 题2 题3 质量管理可能遇到的问题 (1)没有制定质量管理计划&#xff1a; (2)…

JMeter测试工具的简单了解

Apache JMeter 是一款开源的测试工具&#xff0c;主要用于对软件的性能进行测试。它最初被设计用于测试Web应用&#xff0c;但随着时间的推移&#xff0c;它的功能已经扩展到了其他测试领域。 可以应用到的场景 性能测试&#xff1a;评估应用程序在不同负载下的表现。负载测试…

初学代码指南(软2耶)

首先&#xff0c;很高兴又和大家见面了&#xff0c;本文章仅是作者的自我总结&#xff0c;是给笔者看的&#xff0c;所以读者在阅读时请抱着参考的心态&#xff0c;如果觉得可以借鉴的可以稍微借鉴一下&#xff0c;如果觉得笔者写了一坨shi&#xff0c;可以随便喷俺。 一.IDE …