[算法] 拓扑排序

定义

对一个有向无环图(Directed Acyclic Graph,DAG)G进行拓扑排序,使得2任意一对定点uuv,若边(uv)E(G)(u,v)∈E(G),则在线性序列中uu出现在v之前。如下图所示的DAG:
这里写图片描述
一种可能的拓扑排序是:2->8->0->3->7->1->5->6->9->4->11->10->12。

分析

  • 从有向图中选择一个没有前驱(即入度为0)的定点并输出它;
  • 从图中删除该顶点,并且删除从该顶点出发的所有有向边;
  • 重复上述两步,直到剩余的图中所有节点都没有前驱为止。

拓扑排序得到的结果并不唯一

代码实现

假设有向无环图中有nn个节点,可以借助二维数组graph[n][n](邻接矩阵)来表示该图,graph[i][j]graph[i][j]值为1则表示有从ii节点到j节点的有向边,用一维数组indegree[n]indegree[n]表示该图所有节点的入度,将graphgraph中第jj列的所有2相加即得到indegree[j](即第jj个节点的入度),借助队列先进先出的特点来缓存入度为0的节点。代码实现如TopologicalSort.hpp所示:

#ifndef TopologicalSort_hpp
#define TopologicalSort_hpp

#include <queue>
#include <stdio.h>

void topologicalSort(int[][] graph, int nodeCnt, int result) {
    int[] indegree = new int[nodeCnt]; // 用于保存各节点入度
    queue<int> q; // 用来缓存入度为0的节点
    int cur; // 当前从队列头部取出的节点
    int cnt = 0;
    int i;

    // 计算各节点入度
    for (i = 0; i < nodeCnt; i++) { // 第i列
        for (int j = 0; j < nodeCnt; j++) { // 第j行
            indegree[i] += graph[j][i];
        }
    }

    // 缓存初始入度为0的节点
    for (i = 0; i < nodeCnt; i++) {
        if (indegree[i] == 0) {
            q.psuh(i);
        }
    }

    while (!q.empty()) {
        cur = q.front();
        q.pop();
        result[cnt++] = cur; // 保存当前元素到结果中
        for (i = 0; i < nodeCnt; i++) {
            if (graph[cur][i] != 0) {
                // 删除从cur出发的所有边,即相邻节点的入度减1
                if (--indegree[i] == 0) {
                    q.push[i];
                }
            }
        }
    }
}

#endif /* TopologicalSort_hpp */

总结

  • 拓扑排序的本质是不断输出入度为0的节点;
  • 拓扑排序可以用来判断一个有向图中是否存在环,若拓扑排序后得到的结果节点数量少于原图中节点数量,则有向图中存在环;
  • 拓扑排序其实是给定了节点的一组偏序关系;
使用拓扑排序求解有向无环图(DAG)最长路的算法是一种有效的方法。在面对数据量大的情况时,SPFA 或者 Dijkstra + heap 可能会超时,而对于 DAG 图,可以通过拓扑排序(topsort)来优化算法,进而通过 DAG 最短路径(DAGShortestPath)的思路求最长路径 [^1]。 拓扑排序求解 DAG 最长路的基本步骤如下: 1. **拓扑排序**:对 DAG 进行拓扑排序,得到顶的拓扑序列。拓扑排序可以确保对于图中的每条有向边 (u, v),顶 u 在拓扑序列中都出现在顶 v 之前。 2. **初始化距离数组**:创建一个距离数组 dist,用于记录从源到每个顶的最长路径长度。将所有顶的初始距离设为负无穷(通常设为负的一个很大的值),源的距离设为 0。 3. **按拓扑顺序更新距离**:按照拓扑序列依次遍历每个顶 u,对于每个顶 u 的所有出边 (u, v),更新顶 v 的最长路径长度。更新公式为:`dist[v] = max(dist[v], dist[u] + weight(u, v))`,其中 `weight(u, v)` 是边 (u, v) 的权重。 以下是使用 Python 实现的示例代码: ```python from collections import defaultdict, deque def topological_sort(graph, num_vertices): in_degree = [0] * num_vertices for u in graph: for v in graph[u]: in_degree[v] += 1 queue = deque([i for i in range(num_vertices) if in_degree[i] == 0]) top_order = [] while queue: u = queue.popleft() top_order.append(u) for v in graph[u]: in_degree[v] -= 1 if in_degree[v] == 0: queue.append(v) return top_order def dag_longest_path(graph, num_vertices, source): top_order = topological_sort(graph, num_vertices) dist = [-float('inf')] * num_vertices dist[source] = 0 for u in top_order: if dist[u] != -float('inf'): for v, weight in graph[u]: dist[v] = max(dist[v], dist[u] + weight) return dist # 示例图的邻接表表示 graph = defaultdict(list) graph[0] = [(1, 3), (2, 2)] graph[1] = [(2, 4), (3, 2)] graph[2] = [(3, 1)] graph[3] = [] num_vertices = 4 source = 0 longest_paths = dag_longest_path(graph, num_vertices, source) print("从源", source, "到各顶的最长路径长度:", longest_paths) ``` ### 复杂度分析 - **时间复杂度**:拓扑排序的时间复杂度为 $O(V + E)$,其中 $V$ 是顶数,$E$ 是边数。更新距离的过程也需要 $O(V + E)$ 的时间,因此总的时间复杂度为 $O(V + E)$。 - **空间复杂度**:主要用于存储拓扑序列和距离数组,空间复杂度为 $O(V)$。 ### 正确性证明 由于拓扑排序保证了对于每条有向边 (u, v),顶 u 在拓扑序列中先于顶 v 被处理,因此在更新顶 v 的最长路径长度时,顶 u 的最长路径长度已经是最优的。所以,按照拓扑顺序依次更新距离可以得到从源到每个顶的最长路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值