演算法(七)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.
解題思路
根據題目意思我們可知,左邊的數字要在右邊的數字完成後才可完成。我們採用一個容器set放沒有出現在左邊的數字,即不需要等待其他數字先完成的數字。接著遍歷整個vector,遍歷的次數為vector的大小,每次遍歷都找尋一個pair的右邊數字出現在容器set中的,然後從vector中刪除這個pair,因為這個依賴是可以完成的。每次刪除後,要檢視剛剛刪除的pair左邊數字還有沒有出現在容器中某一pair元素的左邊,如果沒有則該數字不依賴任何其他數字,所以可以加入set容器中。重複執行下去,如果最後set的大小為n,vector容器大小為0,則證明可以完成。具體實現程式碼如下:
class
複雜度更好的做法應該是使用拓撲排序。現在我們採用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。
現在採用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; } };
補充:在已知無環的前提下,通過DFS記錄pre值(開始訪問該節點)和post值(子節點全都訪問完),按post值遞減排列,即可得到合法序列。