Python之拓扑排序

拓扑排序的基本概念

拓扑排序(Topological Sorting) 是针对 有向无环图(DAG) 的一种线性排序算法,它使得图中任意一对顶点u和v,若存在边u→v,则在排序中u出现在v的前面。拓扑排序在任务调度、课程安排、依赖关系分析等场景中有广泛应用。🤖🤖


两个条件:

  • 每个顶点出现且只出现一次
  • 若存在边A→B,则在排序中A出现在B的前面

Python实现拓扑排序的两种方法

方法1:Kahn算法(基于入度)

基本思想:

  • 不断从图中选择入度为0的节点
  • 将这些节点加入拓扑序列
  • 从图中删除这些节点及其出边
  • 重复上述过程直到图为空或不存在入度为0的节点

算法步骤:

1,计算图中所有节点的入度

2,将所有入度为0的节点放入队列

3,当队列不为空时:
- a. 取出队首节点u,加入拓扑序列
- b. 对于u的每个邻接节点v:

    - 将v的入度减1

    - 如果v的入度变为0,将v加入队列

4,如果拓扑序列包含所有节点,则排序成功;否则图中存在环

伪代码

function KahnAlgorithm(graph):
    // 计算所有节点的入度
    in_degree = [0 for each node in graph]
    for each node in graph:
        for each neighbor of node:
            in_degree[neighbor] += 1
    
    // 初始化队列
    queue = empty queue
    for each node in graph:
        if in_degree[node] == 0:
            queue.enqueue(node)
    
    // 初始化结果列表
    topo_order = []
    count = 0
    
    // 处理队列
    while queue is not empty:
        u = queue.dequeue()
        topo_order.append(u)
        count += 1
        
        for each neighbor v of u:
            in_degree[v] -= 1
            if in_degree[v] == 0:
                queue.enqueue(v)
    
    // 检查是否有环
    if count != number of nodes in graph:
        return "Graph has at least one cycle"
    else:
        return topo_order

代码实现:

from collections import deque

def topological_sort_kahn(graph):
    # 计算所有节点的入度
    in_degree = {u: 0 for u in graph}
    for u in graph:
        for v in graph[u]:
            in_degree[v] += 1
    
    # 收集所有入度为0的节点
    queue = deque([u for u in in_degree if in_degree[u] == 0])
    topo_order = []
    
    while queue:
        u = queue.popleft()
        topo_order.append(u)
        
        # 移除该节点,并减少邻居节点的入度
        for v in graph[u]:
            in_degree[v] -= 1
            if in_degree[v] == 0:
                queue.append(v)
    
    # 检查是否所有节点都被排序
    if len(topo_order) == len(graph):
        return topo_order
    else:
        return []  # 图中存在环

栗子:
考虑一个有向图:1→2→3

  1. 初始入度:[1:0, 2:1, 3:1]
  2. 将节点1加入队列
  3. 处理1,将2的入度减为0,加入队列
  4. 处理2,将3的入度减为0,加入队列
  5. 处理3
  6. 最终拓扑排序:[1,2,3]

注意事项

如果图中存在环,算法无法完成所有节点的排序

可能有多个有效的拓扑排序结果

适用于静态图,不适合动态变化的图

方法2:DFS算法

概念:
深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树或图的算法。它沿着树的深度遍历节点,尽可能深地搜索树的分支。
基本思想:

  • 从起始节点开始访问
  • 对当前访问节点的所有未访问邻接节点进行递归访问
  • 当没有未访问的邻接节点时回溯

算法特点

使用栈(递归或显式栈)实现

沿着一条路径尽可能深入地搜索

可能需要标记已访问节点以避免重复访问

时间复杂度:邻接表O(V+E),邻接矩阵O(V²)

空间复杂度:O(V)

核心:

如果节点在 temp 中,说明发现了环

如果节点未被访问过:

    添加到 temp 集合

    递归处理所有邻居节点

    从 temp 中移除该节点(回溯)

    标记为永久访问

    将节点加入拓扑序列

代码实现:

def topological_sort_dfs(graph):
    visited = set() #永久标记已完全处理完成的节点
    temp = set()  # 临时标记当前DFS路径上的节点(用于检测环)
    topo_order = [] #存储拓扑排序结果
    
    def dfs(node):
        if node in temp:
            raise ValueError("图中存在环")
        if node not in visited:
            temp.add(node)
            #确保处理图中的所有节点(包括不连通的组件)
            for neighbor in graph.get(node, []):
                dfs(neighbor)
            temp.remove(node)
            visited.add(node)
            topo_order.append(node)
    
    for node in graph:
        if node not in visited:
            dfs(node)
    
    return topo_order[::-1]  
    # DFS拓扑排序是以 后序(post-order)方式将节点加入列表的,
    #即一个节点在其所有依赖之后被添加。
    #而拓扑排序要求每个节点在其依赖之前出现,所以需要反转结果。

使用示例

# 定义有向无环图(邻接表表示)
graph = {
    '起床': ['刷牙', '穿衣'],
    '刷牙': ['吃早餐'],
    '穿衣': ['出门'],
    '吃早餐': ['出门'],
    '出门': [],
    '煮咖啡': ['吃早餐'],
    '看新闻': []
}

# 使用Kahn算法进行拓扑排序
print("Kahn算法结果:", topological_sort_kahn(graph))

# 使用DFS算法进行拓扑排序
print("DFS算法结果:", topological_sort_dfs(graph))

实际应用场景

  1. 任务调度:确定任务执行的先后顺序
  2. 课程安排:处理课程间的先修关系
  3. 软件构建:编译源代码时的依赖关系
  4. 工作流程:确定工作步骤的执行顺序

处理环的情况

当图中存在环时,拓扑排序无法完成。可以在算法中添加检测环的逻辑:

# 修改Kahn算法返回None当存在环时
if len(topo_order) != len(graph):
    print("图中存在环,无法进行拓扑排序")

性能分析

  • 时间复杂度:O(V+E),其中V是顶点数,E是边数
  • 空间复杂度:O(V)

总结: 拓扑排序是解决依赖关系问题的重要算法,
Python的实现简洁高效,适合处理各种任务调度和依赖分析问题。😀😀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值