数据表是否能顺利产出

152 阅读4分钟

问题分析

给定一个关系(relations),每个关系中,第一个元素表示目标节点,后面的元素表示依赖于目标节点的节点。我们要判断这些关系是否构成一个有向图的环路。如果有环,则返回 False;如果没有环,则返回 True

解题思路

这个问题可以转化为一个图的拓扑排序问题。拓扑排序要求图中不存在环路。如果图中存在环,则无法进行有效的拓扑排序。因此,可以通过以下步骤判断图是否存在环路:

  1. 图的构建:我们将关系中的每个目标节点作为图中的节点,将每个依赖关系转化为有向边。

  2. DFS 检测环:我们通过深度优先搜索(DFS)来检查图中是否存在环。DFS 的过程如下:

    • 使用一个 visited 集合标记所有已经完全遍历过的节点。
    • 使用一个 visiting 集合标记当前正在访问的节点。如果在当前路径中再次遇到正在访问的节点,说明图中存在环,返回 False
    • 如果当前节点没有环,继续遍历其邻居节点。
  3. 判断有无环:如果在图的遍历过程中没有发现环,说明图中是有向无环图(DAG),可以返回 True

代码详解

pythonCopy Code
from collections import defaultdict

def solution(relations: list[list[str]]) -> bool:
    # 构建图的邻接表
    graph = defaultdict(list)
    for relation in relations:
        target = relation[0]  # 目标节点
        for dependency in relation[1:]:  # 依赖节点
            graph[dependency].append(target)  # 依赖节点指向目标节点

    # 记录节点的访问状态
    visited = set()  # 存储已经完成遍历的节点
    visiting = set()  # 存储当前正在遍历的节点

    # 定义 DFS 函数来检查环
    def dfs(node):
        # 如果当前节点已经在访问路径中,说明存在环
        if node in visiting:
            return False
        # 如果当前节点已经访问过,直接返回 True
        if node in visited:
            return True

        # 标记当前节点为正在访问
        visiting.add(node)

        # 遍历所有依赖节点,递归访问
        for neighbor in graph[node]:
            if not dfs(neighbor):
                return False

        # 标记当前节点为已访问
        visiting.remove(node)
        visited.add(node)
        return True

    # 对图中每个节点进行 DFS 遍历
    for node in list(graph.keys()):
        if not dfs(node):  # 如果存在环,则返回 False
            return False

    return True  # 如果没有环,则返回 True

if __name__ == "__main__":
    # 测试用例
    print(solution([["A", "B", "C"], ["B", "D"],
                    ["C", "E"], ["D", "A"]]) == False)  # 有环,返回 False
    print(
        solution(
            [
                ["A", "B", "C", "D", "E"],
                ["F", "G", "H", "I"],
                ["J", "K", "L", "M", "A"],
                ["N", "O", "P", "Q"],
                ["E", "H", "I", "J"],
                ["R", "S", "T", "U"],
                ["V", "W", "X"],
                ["Y", "Z"],
            ]
        )
        == False  # 有环,返回 False
    )

代码解释

  1. 图的构建

    • 使用 defaultdict(list) 创建一个图的邻接表。
    • 每一条关系中,第一个元素是目标节点,后面的元素是依赖目标节点的节点。
    • 通过 graph[dependency].append(target) 将依赖节点指向目标节点。
  2. DFS 检测环

    • 使用两个集合 visited 和 visiting 来记录节点的状态。

      • visited 存储已经遍历过且没有环的节点。
      • visiting 存储当前正在递归访问的节点。
    • 如果当前节点已经在 visiting 集合中,说明存在环,返回 False

    • 如果当前节点已经在 visited 集合中,直接跳过(避免重复计算)。

    • 递归地遍历当前节点的所有邻居节点。如果在遍历过程中发现环,立即返回 False

    • 如果遍历完成且没有环,当前节点标记为已访问。

  3. 主函数流程

    • 遍历图中所有节点,对每个节点进行 DFS 检查。
    • 如果有任何一个节点的 DFS 返回 False,则图中存在环,返回 False
    • 如果遍历完所有节点后没有发现环,则返回 True,表示图中是有向无环图。

图解

考虑第一个测试用例:

pythonCopy Code
[["A", "B", "C"], ["B", "D"], ["C", "E"], ["D", "A"]]
  • A 是目标节点,BC 是依赖节点。表示 BC 依赖 A
  • B 是目标节点,D 是依赖节点。表示 D 依赖 B
  • C 是目标节点,E 是依赖节点。表示 E 依赖 C
  • D 是目标节点,A 是依赖节点。表示 A 依赖 D

该图形成了一个环:A -> B -> D -> A,因此返回 False

时间复杂度

  • 每个节点和每条边最多访问一次,因此时间复杂度是 O(V + E) ,其中 V 是图中的节点数,E 是边的数目。

空间复杂度

  • 空间复杂度主要由图的邻接表和 DFS 的递归栈决定。图的邻接表空间复杂度是 O(V + E) ,DFS 递归栈的空间复杂度最坏为 O(V)

结论