拓撲排序及其應用

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;
    }

 

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