代码随想录训练营 Day58打卡 图论part08 拓扑排序 dijkstra(朴素版)

news2024/11/24 14:06:51

代码随想录训练营 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/2127674.html

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

相关文章

用Python实现时间序列模型实战——Day 18: 时间序列中的季节性与周期性预测

一、学习内容 1. 季节性调整与周期性预测 季节性调整 是在时间序列分析中常用的技术&#xff0c;旨在去除数据中因季节性波动导致的周期性变化&#xff0c;使数据更易于解释和预测。通常&#xff0c;我们可以使用季节性分解方法来分离时间序列中的趋势、季节性和随机成分。 …

JAVA实现压缩包解压兼容Windows系统和MacOs

目标&#xff1a;JAVA实现压缩包解压获取图片素材 问题&#xff1a;Windows系统和MacOs压缩出来的zip内容有区别 MacOs会多出来 以及本身一个文件夹 而windows则不会。为了解决这个问题。兼容mac的压缩包增加一层过滤 要知道 ZipInputStream 可以读取 ZIP 文件中的条目&…

KTV 营业明细+员工提成—SAAS本地化及未来之窗行业应用跨平台架构

一、ktv 绩效必要性 1. 激励员工积极性&#xff1a;提成制度能够直接将员工的努力和收入挂钩&#xff0c;促使员工更加积极主动地工作&#xff0c;以获取更高的收入。 2. 提高工作效率和业绩&#xff1a;为了获得更多提成&#xff0c;员工会努力提高工作效率&#xff0c;增加业…

别中招!从“超低利率“到“包过承诺“,揭秘贷款几大陷阱!

今天咱们聊聊贷款时得防的那些坑&#xff0c;免得一不小心就被套路了。你以为找了个靠谱帮手&#xff0c;结果却是步步陷阱&#xff0c;咱们一起来揭秘这些招数&#xff0c;也给大伙儿提个醒。 第一招&#xff0c;低利率诱惑。正常普通信用贷款服务费是2-15%个点内&#xff0c;…

第十七节:学习Hutool上传文件(自学Spring boot 3.x的第四天)

这节记录下如何使用Hutool库上传本地的文件到服务器端&#xff08;因为是练习&#xff0c;所以是本地端&#xff09;。 第一步&#xff1a;引入Hutool库最新版本&#xff0c;通过maven方式。&#xff08;最新版本需去maven仓库查询&#xff09; 第二步&#xff1a;编写一个post…

Django路由访问及查询数据

1、在应用模块下&#xff0c;创建urls文件&#xff0c;用来存放访问路由 2、在项目总访问url里面注册路由 3、在view文件里&#xff0c;定义方法参数 from django.core import serializers from django.db import connection from django.http import HttpResponse, JsonRespo…

【佳学基因检测】在织梦网站中, 创建或修改目录:/var/www/html/cp 失败! DedeTag Engine Create File False

【佳学基因检测】在织梦网站中, 创建或修改目录&#xff1a;/var/www/html/cp 失败&#xff01; DedeTag Engine Create File False 在使用 DedeCMS&#xff08;一个常用的内容管理系统&#xff09;时&#xff0c;如果遇到“创建或修改目录&#xff1a;/var/www/html/cp 失败&…

背靠大众,「半价Model 3」卖爆,小鹏走出低谷

‍作者 |老缅 编辑 |德新 各路消息都在显示&#xff0c;小鹏MONA M03爆单了。 总裁王凤英在庆功宴上喝下了人生第一杯酒。 小鹏MONA产品线负责人透露&#xff0c;上市后的两天内&#xff0c;MONA全国的试驾车累计开了超过10万公里。 在上市后的48小时内&#xff0c;M03获得…

华为OD机试 - 周末爬山 - 广度优先搜索BFS(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

Mysql基础——DML

数据操作语言&#xff08;DML&#xff0c;Data Manipulation Language&#xff09; DML语句用于对数据库中的数据进行操作&#xff08;增删改查数据&#xff09;&#xff0c;主要包括&#xff1a; INSERT&#xff1a;向表中插入数据&#xff0c;例如 INSERT INTO table_name …

超简单,3步训练Flux Lora模型,附整合包!

超简单&#xff0c;3步训练Flux Lora模型&#xff0c;附整合包&#xff01; &#x1f389; 12G显存也能炼Flux Lora模型&#xff1f;&#xff01;3步速成&#xff0c;小白也能轻松上手&#xff01; 兄弟们&#xff01;AI绘画领域又迎来了一波革命&#xff01;Flux Lora模型训练…

2024中国500强企业高峰论坛安然大健康分论坛圆满举办!

一场巅峰聚首的风云际会&#xff0c;一次引领未来的行业盛宴。 9月10日至11日&#xff0c;由中国企业联合会、中国企业家协会主办的2024中国500强企业高峰论坛在天津举行&#xff0c;本届高峰论坛以“向‘新’而行、打造更多世界一流企业”为主题&#xff0c;汇集业内知名企业…

利用AI驱动智能BI数据可视化-深度评测Amazon Quicksight(三)

简介 随着生成式人工智能的兴起&#xff0c;传统的 BI 报表功能已经无法满足用户对于自动化和智能化的需求&#xff0c;今天我们将介绍亚马逊云科技平台上的AI驱动数据可视化神器 – Quicksight&#xff0c;利用生成式AI的能力来加速业务决策&#xff0c;从而提高业务生产力。…

【Qt应用】Qt编写简易登录注册界面

目录 引言 一、准备工作 二、设计思路 2.1 登录 2.2 注册 三、登录功能 3.1 创建账号与密码输入框 3.2 设置密码输入框格式 3.2.1 初始 3.2.2 创建一个显示隐藏按钮 3.2.3 信号与槽函数 3.3 创建登录按钮 3.4 创建可选功能 四、注册功能 4.1 账号和密码输入…

【Go】深入探索Go语言中运算符

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

路基边坡自动化监测解决方案

物联网云平台 平台登录--用户登录 输入网址&#xff1a;http://yun.sj2000.org.cn&#xff0c;进入系统登录界面&#xff0c;输入用户名及密码后进入系统平台。 设备详情--设备概览 登录系统平台后&#xff0c;用户可在界面左侧看到系统项目栏和子项目选项&#xff0c;登陆的…

【Python爬虫系列】_018.多线程与多进程

我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈 PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈 Oracle数…

3. 进阶指南:自定义 Prompt 提升大模型解题能力

怎么判断 Prompt 的好坏&#xff0c;有什么问题有着标准答案么&#xff1f; 答&#xff1a;让大模型求解数学问题。 李宏毅老师的 HW4 正好提到了有关数学问题的 Prompt&#xff0c;所以我决定中间插一篇这样的文章。通过本文你将&#xff1a; 了解各种 Prompt 如何影响大型语言…

基于asp.net电子邮件系统设计与实现

1 引言 1&#xff0e;1 电子邮件介绍 电子邮件(简称E-mai1)又称电子信箱、电子邮政&#xff0c;它是—种用电子手段提供信息交换的通信方式。它是全球多种网络上使用最普遍的一项服务。这种非交互式的通信,加速了信息的交流及数据传送,它是—个简易、快速的方法。通过连接全…

个人用户如何有效利用固态硬盘数据恢复工具

固态硬盘相较于机械硬盘来说更为小巧&#xff0c;所以很多人选择使用固态硬盘来进行数据的存储。这些存储设备都可能会遇到一些意外导致的数据丢失情况。好在现在的科技比较发达&#xff0c;这次我们来聊一聊有哪些固态硬盘数据恢复工具可以解决这个问题吧。 1.福晰数据恢复 …