1. 程式人生 > >演算法(七)Course Schedule 拓撲排序

演算法(七)Course Schedule 拓撲排序

題目描述

There are a total of n courses you have to take, labeled from 0 to n - 1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

For example:

2, [[1,0]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.

2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

解題思路

  1. 根據題目意思我們可知,左邊的數字要在右邊的數字完成後才可完成。我們採用一個容器set放沒有出現在左邊的數字,即不需要等待其他數字先完成的數字。接著遍歷整個vector,遍歷的次數為vector的大小,每次遍歷都找尋一個pair的右邊數字出現在容器set中的,然後從vector中刪除這個pair,因為這個依賴是可以完成的。每次刪除後,要檢視剛剛刪除的pair左邊數字還有沒有出現在容器中某一pair元素的左邊,如果沒有則該數字不依賴任何其他數字,所以可以加入set容器中。重複執行下去,如果最後set的大小為n,vector容器大小為0,則證明可以完成。具體實現程式碼如下:

    class
    Solution { public: bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) { set <int> hasFinished; //兩個for迴圈初始化容器set for (int i = 0; i < numCourses; ++i) hasFinished.insert(i); vector< pair<int, int> >::iterator iter = prerequisites.begin(); for (; iter != prerequisites.end(); ++iter) { hasFinished.erase(iter->first); } int number = prerequisites.size(); for (int i = 0; i < number; ++i) { for (iter = prerequisites.begin(); iter != prerequisites.end(); ++iter) { if (hasFinished.find(iter->second) != hasFinished.end()) { int temp = iter->first; prerequisites.erase(iter); //該數字是否不再依賴其他數字,如果是加入容器set中。 int k = 0; for (k = 0; k < prerequisites.size(); ++k) { if (prerequisites[k].first == temp) { break; } } if (k == prerequisites.size()) { hasFinished.insert(temp); } break; } } //下面這兩行的目的是當遍歷vector時沒有可剔除的則可直接退出,不必再迴圈下去。但是加上之後是錯的,因為當刪除vector元素時iter已經失效。 //if (iter == prerequisites.end()) // break; } if (hasFinished.size() == numCourses) return true; return false; } };
  2. 複雜度更好的做法應該是使用拓撲排序。現在我們採用DFS,程式碼如下:

    class Solution {
    public:
        bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
            vector<bool> onpath(numCourses, false), visited(numCourses, false);
            for (int i = 0; i < numCourses; i++)
                if (!visited[i] && dfs_cycle(graph, i, onpath, visited))
                    return false;
            return true;
        }
    private:
        //構建一個圖
        vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph(numCourses);
            for (auto pre : prerequisites)
                graph[pre.second].insert(pre.first);
            return graph;
        } 
        //從某個節點開始進行深度優先遍歷
        bool dfs_cycle(vector<unordered_set<int>>& graph, int node, vector<bool>& onpath, vector<bool>& visited) {
            if (visited[node]) return false;
            onpath[node] = visited[node] = true; 
            for (int neigh : graph[node])
                if (onpath[neigh] || dfs_cycle(graph, neigh, onpath, visited))
                    return true;
            return onpath[node] = false;
        }
    };

    解析:如果拓撲排序是成功的,則從一個點開始的深度優先遍歷不會遍歷到已經在遍歷路徑上的節點,否則就構成了一個環。所以用onpath記錄每個節點是否在路徑上。如果從第一個節點出發深度遍歷,路徑上不出現重複點則沒有環。然後再查詢有無沒遍歷過的節點,已遍歷過的節點無需再遍歷,因為該節點路徑上的點都已被遍歷。如果有還沒遍歷過的點則進行遍歷,同樣是不能有環的存在。但是此次遍歷路徑上可能會有已遍歷過的點,這時對於該已遍歷的點無需再次遍歷。(因為他指向的節點時可以先完成,然後它自然也可以跟著完成)同時,每次深度優先遍歷完要把onpath值重新設定為false。

  3. 現在採用BFS來完成拓撲排序。記錄每個節點的入度數,然後通過迴圈將入度為0的數去掉並將其指向的節點入度減一。程式碼如下:

    class Solution {
    public:
        bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
            vector<int> degrees = compute_indegree(graph);
            for (int i = 0; i < numCourses; i++) {
                int j = 0;
                for (; j < numCourses; j++)
                    if (!degrees[j]) break;
                if (j == numCourses) return false;
                degrees[j] = -1;
                for (int neigh : graph[j])
                    degrees[neigh]--;
            }
            return true;
        }
    private:
        vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph(numCourses);
            for (auto pre : prerequisites)
                graph[pre.second].insert(pre.first);
            return graph;
        }
        vector<int> compute_indegree(vector<unordered_set<int>>& graph) {
            vector<int> degrees(graph.size(), 0);
            for (auto neighbors : graph)
                for (int neigh : neighbors)
                    degrees[neigh]++;
            return degrees;
        }
    };
  4. 補充:在已知無環的前提下,通過DFS記錄pre值(開始訪問該節點)和post值(子節點全都訪問完),按post值遞減排列,即可得到合法序列。