拓扑排序是一种用于有向无环图(DAG)的排序算法,其核心思想是将图中所有顶点排成一个线性序列

拓扑排序是一种用于有向无环图(DAG)的排序算法,其核心思想是将图中所有顶点排成一个线性序列,使得对于每一条有向边 (u, v),顶点 u 在序列中都出现在 v 之前。这一方法广泛应用于任务调度、课程安排、编译顺序等场景。

拓扑排序的基本步骤如下:

  1. 找出所有入度为 0 的顶点(即没有前驱的顶点),并将它们加入队列;
  2. 从队列中取出一个顶点输出,并删除该顶点及其发出的所有边;
  3. 更新这些边所指向顶点的入度,若某顶点入度变为 0,则将其入队;
  4. 重复上述过程,直到队列为空;
  5. 若输出的顶点数等于总顶点数,则说明图无环,排序成功;否则存在环,无法进行拓扑排序。

实现方式主要有两种:

  • Kahn 算法(基于入度):使用队列维护入度为 0 的节点,适合广度优先处理。
  • DFS 法:对图进行深度优先遍历,当某个顶点的所有邻接点都被访问后,将该顶点压入栈中。最后依次弹出栈中元素即为逆拓扑序列。

时间复杂度分析:

  • 每个顶点和边仅被访问一次,因此时间复杂度为 $ O(n + e) $,其中 $ n $ 为顶点数,$ e $ 为边数。

AOE 网(Activity On Edge Network)详解:

AOE 网是一种带权有向无环图:

  • 顶点表示事件(event),代表某些活动的完成;
  • 有向边表示活动(activity),边上的权值表示活动所需的时间;
  • 整个图表示一个工程的流程。

主要用途包括:

  1. 计算整个工程的最早完成时间(即关键路径长度);
  2. 找出关键路径:从源点到汇点的最长路径,路径上的活动称为关键活动,它们的延迟会直接影响整个工期;
  3. 优化资源分配:非关键活动有一定的“松弛时间”(float),可适当延迟而不影响总工期,便于资源调配。

关键路径分析涉及的概念:

  • ve(j):事件 j 的最早发生时间;
  • vl(j):事件 j 的最迟发生时间;
  • e(i):活动 i 的最早开始时间;
  • l(i):活动 i 的最迟开始时间;
  • l(i) - e(i) = 0 的活动即为关键活动。

通过正向和反向两次遍历计算各事件和活动的时间参数,最终确定关键路径。


示例解析:若某 AOV 网的拓扑序列为 6,1,4,3,2,5,说明该图可以线性化,不存在回路,排序有效。

from collections import deque, defaultdict

def topological_sort(n, edges):
    # 建图并统计入度
    indegree = [0] * (n + 1)
    graph = defaultdict(list)
    
    for u, v in edges:
        graph[u].append(v)
        indegree[v] += 1
    
    # 入度为0的节点入队
    queue = deque([i for i in range(1, n + 1) if indegree[i] == 0])
    result = []
    
    while queue:
        u = queue.popleft()
        result.append(u)
        for v in graph[u]:
            indegree[v] -= 1
            if indegree[v] == 0:
                queue.append(v)
    
    return result if len(result) == n else []  # 空列表表示有环

# 示例调用
edges = [(6,1), (1,4), (4,3), (3,2), (2,5)]  # 简化示例边
print("拓扑序列:", topological_sort(6, edges))  # 输出可能包含孤立点

使用深度优先搜索(DFS)实现拓扑排序的核心思想是:在对有向无环图进行 DFS 遍历时,每当一个顶点的所有邻接点都被访问完毕后,将其加入结果栈中。最后将栈逆序输出,即可得到拓扑序列

这种方法得到的是逆拓扑序列的反向积累,因此最终需要反转栈的结果或从尾到头读取。


✅ 算法步骤(基于 DFS 的拓扑排序):

  1. 对图中每个未被访问的顶点调用 DFS;
  2. 在 DFS 过程中,先递归访问所有邻接顶点;
  3. 当该顶点的所有邻接点都处理完成后,将该顶点压入
  4. 所有顶点遍历完成后,栈中元素从顶到底即为逆向拓扑序列
  5. 弹出栈中所有元素,得到正向拓扑序列。

⚠️ 注意:此方法仅适用于 有向无环图(DAG)。若图中存在环,则无法生成有效的拓扑序列。


🧩 状态标记说明(检测环):

为了在 DFS 中检测是否存在环,需维护三种状态:

  • 0:未访问
  • 1:正在访问(当前 DFS 路径上的节点)
  • 2:已访问且已完成回溯

若在 DFS 中遇到一个状态为 1 的节点,说明存在环。


✅ Python 实现代码:

def dfs_topological_sort(n, edges):
    from collections import defaultdict

    # 建图
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)

    visited = [0] * (n + 1)  # 0:未访问, 1:搜索中, 2:已完成
    stack = []
    has_cycle = [False]

    def dfs(u):
        if visited[u] == 1:  # 发现回溯边,存在环
            has_cycle[True] = True
            return
        if visited[u] == 2:
            return

        visited[u] = 1  # 标记为正在访问
        for v in graph[u]:
            dfs(v)
        visited[u] = 2  # 标记为已完成
        stack.append(u)  # 完成后入栈

    for i in range(1, n + 1):
        if visited[i] == 0:
            dfs(i)
        if has_cycle[0]:
            break

    if has_cycle[0]:
        return []  # 存在环,无法拓扑排序
    return stack[::-1]  # 反转栈得拓扑序列

# 示例使用
edges = [(6,1), (1,4), (4,3), (3,2), (2,5)]
print("DFS 拓扑排序结果:", dfs_topological_sort(6, edges))

🔍 时间复杂度分析:

  • 每个顶点和边只被访问一次 → $ O(n + e) $
  • 空间复杂度:$ O(n + e) $(图存储 + 递归栈)

💡 示例解释:

假设图结构对应关系如下:

  • 6 → 1 → 4 → 3 → 2 → 5
    则 DFS 可能按退出顺序依次将 5, 2, 3, 4, 1, 6 入栈,最终栈反转后为 [6,1,4,3,2,5],符合示例中的拓扑序列。

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bol5261

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值