LeetCode 207. 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?
Example 1:
Input: 2, [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take.
To take course 1 you should have finished course 0. So it is possible.
Example 2:
Input: 2, [[1,0],[0,1]] Output: false Explanation: 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.
Note:
- The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
- You may assume that there are no duplicate edges in the input prerequisites.
題目解釋:
輸入包括一個整數代表圖的大小,還有一個vector儲存有向圖的邊,要求如果一個節點A指向另一個節點B,那麼節點B就不能出現在節點A的前面。
這是一個很經典的拓撲排序問題。
拓撲排序
首先,先介紹一下拓撲排序的含義;
拓撲排序是圖論中一個很經典的演算法;對於一個有向無環圖G
進行拓撲排序,就是將G
中的所有節點排成一個線性序列;對於圖G
中的每一條邊,如邊(u,v)
,則表示在我們輸出的線性序列中節點u
必須出現在節點v
的前面,這個線性序列就被我們稱為滿足拓撲排序序列,簡稱拓撲序列。
需要注意的是,拓撲排序是針對有向無環圖的;
拓撲排序的實現也並不複雜:
(1)首先,我們需要選取圖中一個入度為0的節點(入度表示某點作為圖中邊的終點的次數之和)
(2)然後從圖中刪除以該節點為起點的所有有向邊和該節點,此時被刪除的有向邊的終點的入度減 1;
(3)重複上面的(1)和(2)操作直到圖中沒有入度為0的節點;
當演算法結束後,如果圖中仍然還有節點,那麼說明該有向圖不存在拓撲序列;反之,存在拓撲序列;
下圖是拓撲排序的一個簡單例子:
由此可以得到拓撲序列{1,2,3,4}
我們還可以看到,在第二步移除節點2和移除節點3的效果是一樣的,因此拓撲排序並不是唯一的。
講完了拓撲排序的原理,下面就是程式碼實現
程式碼實現
程式碼解釋已經註釋了,應該不難理解:
/************************Graph類************************/
class Graph{
private:
int nums; // 圖的頂點個數
std::vector<int>* adj; // 鄰接表儲存有向邊
vector<int> indegree; // 記錄每個節點的入度
public:
/*
* 建構函式
* 輸入:圖的個數
*/
Graph(int v){
nums = v;
// 初始化鄰接表和每個節點的入度
adj = new std::vector<int>[nums];
for (int i = 0; i < v; ++i)
{
indegree.push_back(0);
}
}
/* 解構函式 */
~Graph(){
delete []adj;
}
/*
* 函式說明:新增有向邊
* 輸入:
* from: 起始點
* to: 終點
*/
void addEdge(int from,int to){
// 往鄰接表中新增有向邊
adj[from].push_back(to);
// 修改終點的入度
indegree[to]++;
}
/*
* 函式說明:判斷是否存在拓撲序列
* 輸出:布林值,true表示存在拓撲序列
*/
bool topological_sort(){
// 使用一個佇列儲存入度為0的節點
queue<int> q;
for(int i = 0;i < nums;i++){
if(indegree[i] == 0){
q.push(i);
}
}
// count記錄已經從圖中刪除的節點個數,當count等於節點個數,說明存在拓撲序列
int count = 0,temp;
while(!q.empty()){ // 跳出迴圈條件:圖中已經不存在入度為0的節點
temp = q.front();
q.pop();
count++;
// 更新入度和儲存入度為0的節點的佇列
for(auto value:adj[temp]){
indegree[value]--;
if(indegree[value] == 0){
q.push(value);
}
}
}
return (count == nums);
}
};
/************************Solution類************************/
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
// 初始化圖
Graph graph(numCourses);
for(auto value:prerequisites){
graph.addEdge(value.first,value.second);
}
// 拓撲排序
return graph.topological_sort();
}
};