Bootstrap

代码随想录算法训练营第五十八天 | 拓扑排序精讲、dijkstra(朴素版)精讲

一、拓扑排序精讲

题目连接:117. 软件构建 (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——拓扑排序

拓扑排序定义:给出一个 有向图,把这个有向图转成线性的排序 就叫拓扑排序。同时拓扑排序也是需要检测该有向图是否有环,即存在循环依赖的情况,如果有,则不能做线性排序。

特点:有向无环图 先行排序

思路:
step1. 找入度为 0 的节点,确定出发节点,加入结果集
step2. 将该节点从图中移除
结果集中的顺序就是想要的拓扑排序顺序。

Note:结果集中的顺序可能不唯一。

from collections import deque, defaultdict

# n -> 代表图中节点数,即文件个数
# edges -> 包含所有依赖关系的二元组(s, t),表示 t 文件依赖于 s 文件
def topological_sort(n, edges):
    inDegree = [0] * n          # 记录每个文件的入度
    umap = defaultdict(list)    # 记录每个文件的依赖关系
    
    # 构造图和入度表
    for s, t in edges:
        # 表示有一个新的文件依赖 t 
        inDegree[t] += 1
        # 将 t 添加到 s 的依赖列表中,表示要删 t 需要先删 0
        umap[s].append(t)
        
    # 初始化队列,加入所有入度为0的节点
    que = deque()
    for i in range(n):
        if inDegree[i] == 0:
            que.append(i)
            
    result = []
    
    # 列表不为空时,表示有文件可以继续处理
    while que:
        cur = que.popleft()
        result.append(cur)
        # 获取当前文件指向的文件
        for file in umap[cur]:
            # 当前文件入度 -1
            inDegree[file] -= 1
            if inDegree[file] == 0:
                que.append(file)
                
    if len(result) == n:
        print(" ".join(map(str, result)))
    else:
        print(-1)
        

if __name__ == '__main__':
    n, m = map(int, input().split())
    edges = [tuple(map(int, input().split())) for _ in range(m)]
    topological_sort(n, edges)

二、dijkstra(朴素版)精讲

题目连接:47. 参加科学大会(第六期模拟笔试) (kamacoder.com)
​​​​​​​文章讲解:代码随想录 (programmercarl.com)——dijkstra(朴素版)

dijkstra算法用法:在 有权图 中求从起点到其他节点的最短路径。

Notes:
​​​​​​​1. dijkstra算法可以同时求 起点到所有节点的最短路径
2. 权重不能为负数

dijkstra算法三部曲:
step1. 选择距离源节点近且未被访问过的节点;
step2. 将该节点标记为访问过;
step3. 更新非访问节点到远点的距离,即更新 minDist 数组。

minDist数组用来记录每一个节点距离源点的最小距离。

# edges -> (p1, p2, val)的列表,其中 p1 为起始节点编号,p2 终止节点编号,val 权重
def dijkstra(n, m, edges, start, end):
    # 初始化邻接矩阵
    grid = [[float('inf')] * (n + 1) for _ in range(n + 1)]
    
    # 填充邻接矩阵
    for p1, p2, val in edges:
        grid[p1][p2] = val
    
    # 初始化距离数组和访问数组
    minDist = [float('inf')] * (n + 1)
    visited = [False] * (n + 1)
    
    minDist[start] = 0
    
    # 遍历所有节点
    for _ in range(1, n + 1):
        minVal = float('inf')
        cur = -1
        
        # step1. 选择距离源节点近且未被访问过的节点
        for v in range(1, n + 1):
            if not visited[v] and minDist[v] < minVal:
                minVal = minDist[v]
                cur = v
                
        # 如果找不到未访问的节点,结束
        if cur == -1:
            break
        
        # step2. 将该节点标记为访问过
        visited[cur] = True
        
        # step3. 更新非访问节点到远点的距离
        for v in range(1, n + 1):
            # 检查节点 v 是否未访问且存在边从 cur 到 v,同时检查通过 cur 到达 v 的新路径是否更短
            # grid[cur][v] 表示从节点 cur 到 节点 v 的权重
            if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]:
                minDist[v] = minDist[cur] + grid[cur][v]
            
    # 如果目标节点不可到达    
    if minDist[end] == float('inf'):
        return -1
    else:
        return minDist[end]
        

if __name__ == '__main__':
    n, m = map(int, input().split())
    
    edges = []
    
    for _ in range(m):
        p1, p2, val = map(int, input().split())
        edges.append((p1, p2, val))
        
    start = 1
    end = n
    
    result = dijkstra(n, m, edges, start,end)
    
    print(result)
def dijkstra(n, m, edges, start, end):
    grid = [[float('inf')] * (n + 1) for _ in range(n + 1)]
    
    for p1, p2, val in edges:
        grid[p1][p2] = val
    
    minDist = [float('inf')] * (n + 1)
    visited = [False] * (n + 1)
    
    # 新增数组用于记录前驱节点
    prev = [-1] * (n + 1)  
    
    minDist[start] = 0
    
    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):
            if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]:
                minDist[v] = minDist[cur] + grid[cur][v]
                # 更新前驱节点为当前处理的节点
                prev[v] = cur  
            
    if minDist[end] == float('inf'):
        return -1, []
    else:
        path = []
        current = end
        # 回溯路径
        while current != -1: 
            path.append(current)
            current = prev[current]
            
        # 将路径反转,得到从起点到终点的顺序
        path.reverse()
        return minDist[end], path

if __name__ == '__main__':
    n, m = map(int, input().split())
    
    edges = []
    
    for _ in range(m):
        p1, p2, val = map(int, input().split())
        edges.append((p1, p2, val))
        
    start = 1
    end = n
    
    result, path = dijkstra(n, m, edges, start, end)
    
    # 输出结果
    if result == -1:
        print(-1)
    else:
        print("->".join(map(str, path)))
;