拓扑排序及其应用

AOV网的概念:

AOV网是有向无环图的一种,全称Activity On Vertex Network,顾名思义 活动在顶点的网,使用顶点表示活动,顶点之间的边表示活动的先后顺序,例如<a,b>有一条从a到b的边,表示a活动发生在b之前。

拓扑排序:

在AOV网没有环路的情况下,将这些活动排成一个满足所有边的先后顺序条件线性序列。

排序算法:

1)选择AOV网中一个没有前驱的结点输出;

2)将输出结点从图中删除(并删除与当前结点相连的所有边)

3)重复1)2)直到图中没有结点。

应用:环的判断

对AOV网进行拓扑排序,得到的拓扑有序序列包含所有结点时,证明该AOV网没有环;否则有环。一个例子如下 

 对于上图情况,由于三个结点都存在自己的前驱,因此不能被访问到,从而证明有环存在。

课程表排课问题:

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:使用拓扑排序

该问题说白了就是判断当前图是否有环的问题,我们就可以使用拓扑排序求解。不过并不用真的从图中删除结点和边,只需要维持一个入度数组即可,当一个结点的入度为0时则说明其没有前驱,我们通过这种方式进行拓扑排序。

实现代码如下;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adja = new ArrayList<>(numCourses);//临接矩阵
        boolean[] access = new boolean[numCourses]; 
        int[] inDegree = new int[numCourses]; // 入度数组
        for(int i = 0; i < numCourses; i++){
            adja.add(new ArrayList<>());
        }
        for(int[] p : prerequisites){
            for(int i = 1; i < p.length; i++){
                adja.get(p[i]).add(p[0]);
                inDegree[p[0]]++;
            }
        }
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; i++){
            if(inDegree[i] == 0){
                queue.add(i);
            }
        }

        while(!queue.isEmpty()){
            int cur = queue.remove();
            if(access[cur]){
                continue;
            }
            access[cur] = true;
            for(int next : adja.get(cur)){
                if(--inDegree[next] == 0){
                    queue.add(next);
                }
            }
        }
        for(int i = 0; i < numCourses; i++){
            if(!access[i]){
                return false;
            }
        }
        return true;
    }

解法二:

由于该问题可以转化为判断有向图是否有环问题,因此用经典判断图是否有环的三色标记法处理,该方法是对图进行dfs遍历中使用三种颜色标记此时结点的状态,姑且用-1,0,1来表示三种状态,

-1表示当前结点未被访问,

0表示当前结点(及其后继)正在被访问,

1表示当前结点及其后继已经被访问了

因此在dfs遍历过程中访问前若出现当前结点的状态为0时,说明由当前结点的后继可以到达当前结点,如此则证明该有向图是存在环的。实现代码如下:

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adja = new ArrayList<>(numCourses);
        int[] access = new int[numCourses]; // -1 0 1 未访问 -1 正在访问 0 访问完了 1
        for(int i = 0; i < numCourses; i++){
            adja.add(new ArrayList<>());
            access[i] = -1;
        }
        for(int[] p : prerequisites){
            for(int i = 1; i < p.length; i++){
                adja.get(p[i]).add(p[0]);
            }
        }
        for(int i = 0; i < numCourses; i++){
            if(access[i] == 1){
                continue;
            }
            if(isCicle(i, adja, access)){ //有环则说明无法修完
                return false;
            }
        }
        return true;

    }
    public boolean isCicle(int cur, List<List<Integer>> adja, int[] access){
        if(access[cur] == 1){
            return false;
        }
        if(access[cur] == 0){
            return true;
        }
        access[cur] = 0;
        boolean result = false;
        for(int next : adja.get(cur)){
            result = result || isCicle(next, adja, access);
        }
        access[cur] = 1;
        return result;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章