「图论」拓扑排序算法——Kahn算法和DFS算法

直观地说就是,让你把一幅图「拉平」,而且这个「拉平」的图里面,所有箭头方向都是一致的,比如上图所有箭头都是朝右的。

对于任何有向图而言,其拓扑排序为其所有结点的一个线性排序(对于同一个有向图而言可能存在多个这样的结点排序)。该排序满足这样的条件——对于图中的任意两个结点uv,若存在一条有向边从u指向v,则在拓扑排序中u一定出现在v前面。

拓扑排序可以用来判断图中是否有环, 还可以用来判断图是否是一条链。

算法思路

拓扑排序是基于 有向图 无环图的

假如你是一个编译器,你需要编译以下命令。

A B C D E 这几条命令。 A是依赖B,C的,C依赖D,E,B依赖E,这时候你需要找到一条不需要依赖其他命令的那条命令首先编译他,之后的才能编译。 这时候我们用拓扑排序就可以找到依赖关系最小的那个顶点在这个图里就是D E。依次排序输出。

思路

找到入度为0的点,然后把他储存后在图里删掉。

那么这个点的nexts里面的点入度就减少1

继续找入度为0的点重复上述步骤。

####算法的具体设计

#####实现一:Kahn算法(BFS广度优先遍历)

保存他的所有节点对应的入度。 用一个队列保存每次入度为0的节点然后pop,pop的顺序就是拓扑排序的顺序。

public class topoSort{
    /**
     * 拓扑排序
     * @param adjacency 是一个邻接表
     */
    public void BFS(List<List<Integer>> adjacency){
        //入度为 0 的节点队列
        Queue<Integer> queue = new LinkedList<>();
        //节点的总个数
        int n = adjacency.size();
        //邻接表
        int[] indegrees = new int[n];
        for(List<Integer> nodes: adjacency){
            //构造入度表
            //.....
        }
        for(int i = 0;i<n;i++)
            if(indegrees[i] == 0) queue.add(i);

        // BFS TopSort.
        while(!queue.isEmpty()){
            int pre = queue.poll();
            System.out.println(pre);
            n--;
            for(int cur: adjacency.get(pre)){
                if(--indegrees[cur] == 0) queue.add(cur);
            }
        }
    }
}

####实现二:DFS

public class Solution{
    /**
     * https://leetcode-cn.com/problems/course-schedule/
     * 拓扑排序
     */
    public boolean canFinish(int numCourses,int[][] prerequisites){
        List<List<Integer>> adjacency = new ArrayList<>();
        for(int i = 0;i<numCourses;i++)
            adjacency.add(new ArrayList<>());
        int[] flags = new int[numCourses];
        for(int[] cp: prerequisites)
            adjacency.get(cp[1]).add(cp[0]);
        for(int i = 0;i<numCourses;i++)
            if(!dfs(adjacency,flags,i)) return false;
        return true;
    }

    /**
     * 借助一个标志列表 flags,用于判断每个节点 i 的状态:
     * 未被 DFS 访问:i == 0;
     * 已被其他节点启动的 DFS 访问:i == -1;
     * 已被当前节点启动的 DFS 访问:i == 1。
     * 对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。DFS 流程;
     * 终止条件:
     * 当 flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
     * 当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即 课程安排图有环 ,直接返回 False。
     * 将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过;
     * 递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False;
     * 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1−1 并返回 True。
     * 若整个图 DFS 结束并未发现环,返回 True。
     *
     */
    private boolean dfs(List<List<Integer>> adjacency,int[] flags,int i){
        if(flags[i] == 1) return false;
        if(flags[i] == -1) return true;
        flags[i] = 1;
        for(Integer j: adjacency.get(i))
            if(!dfs(adjacency,flags,j)) return false;
        flags[i] = -1;
        return true;
    }
}
Licensed under CC BY-NC-SA 4.0
Last updated on Mar 23, 2024 06:11 UTC
让过去的过去,给时间点时间
Built with Hugo
Theme Stack designed by Jimmy