Skip to content

检测循环依赖

约 597 字大约 2 分钟

拓扑排序

2025-02-28

循环依赖检测。 如

[['A', 'B'], ['B', 'C'], ['C', 'D'], ['B', 'D']] => false,[['A', 'B'], ['B', 'C'], ['C', 'A']] => true

或者

小王写了一个makefile,其中有n个编译项编号为0~n-1,他们互相之间有依赖关系。请写一个程序解析依赖,给出一个可行的编译顺序。

拓扑排序

遍历输入数组以构建图的邻接表,入度数组。只存入度为0的节点的队列,结果链表存队列弹出的节点。从队列中取出一个节点, 将该节点添加到结果列表,遍历该节点的邻接节点,并减少它们的入度,如果邻接节点的入度变为0,则加入队列。

类似BFS。

public class Solution {
    public List<Integer> haveCircularDependency(int n, int[][] prerequisites) {
        List<List<Integer>> g = new ArrayList<>(); // 用于表示图的邻接表
        int[] indeg = new int[n]; // 用于记录每个节点的入度
        List<Integer> res = new ArrayList<>(); // 用于存储拓扑排序的结果

        // 初始化邻接表
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }
        // 构建图的邻接表并更新入度数组
        for (int[] prerequisite : prerequisites) {
            int a = prerequisite[0], b = prerequisite[1];
            g.get(a).add(b); // 在邻接表中添加边
            indeg[b]++; // 增加节点b的入度
        }

        // 创建队列用于拓扑排序。队列和res都是存储入度为0的节点,队列只是过渡。
        Queue<Integer> q = new LinkedList<>();
        // 将所有入度为0的节点加入队列
        for (int i = 0; i < n; i++) {
            if (indeg[i] == 0) {
                q.offer(i);
            }
        }
        
        // 进行拓扑排序
        while (!q.isEmpty()) {
            int t = q.poll(); // 从队列中取出一个节点。有出队就要考虑入队,循环流动。
            res.add(t); // 将该节点添加到结果列表。注意返回的是代表操作顺序的列表,不是true或false。

            // 出完队,要减少邻接节点的入度。
            // 遍历该节点的邻接节点,并减少它们的入度
            for (int j : g.get(t)) {
                indeg[j]--; // 减少邻接节点的入度
                if (indeg[j] == 0) {
                    q.offer(j); // 如果邻接节点的入度变为0,则加入队列
                }
            }
        }

        // 检查是否所有节点都在拓扑排序结果中
        if (res.size() == n) {
            return res; // 如果是,返回拓扑排序结果
        } else {
            return new ArrayList<>(); // 如果不是,说明存在循环依赖,返回空列表
        }
    }
}