1、題目描述
現在你總共有 n 門課需要選,記爲 0 到 n-1。
在選修某些課程之前需要一些先修課程。 例如,想要學習課程 0 ,你需要先完成課程 1 ,我們用一個匹配來表示他們: [0,1]
給定課程總量以及它們的先決條件,返回你爲了學完所有課程所安排的學習順序。
可能會有多個正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個空數組。
說明:
- 輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請參見圖的表示法。
- 你可以假定輸入的先決條件中沒有重複的邊。
2、示例
輸入: 2, [[1,0]]
輸出: [0,1]
解釋: 總共有 2 門課程。要學習課程 1,你需要先完成課程 0。因此,正確的課程順序爲 [0,1] 。
示例 2:輸入: 4, [[1,0],[2,0],[3,1],[3,2]]
輸出: [0,1,2,3] or [0,2,1,3]
解釋: 總共有 4 門課程。要學習課程 3,你應該先完成課程 1 和課程 2。並且課程 1 和課程 2 都應該排在課程 0 之後。因此,一個正確的課程順序是 [0,1,2,3] 。另一個正確的排序是 [0,2,1,3] 。
3、題解
解法一:
基本思想:拓撲排序,給定一個包含n個節點的有向圖G,給出它的節點編號的一種排列,如果滿足:對於圖G中的任意一條有向邊(u,v),u在排列中都出現在v的前面,稱該排列是圖G的拓撲排序。
基本思想:廣度優先搜索
- 藉助一個隊列queue,將所有入度爲0的節點入隊
- 當queue非空時,依次將隊首節點cur出隊同時加入res,執行numCourses--,並將cur的所有後繼節點的入度 - 1,如果後繼節點的入度爲0則也入隊。
- 若整個課程安排圖是有向無環圖,則所有節點一定都入隊並出隊過,最後numCourses = 0,即完成拓撲排序返回res。
- 若課程安排圖中存在環路,一定有節點的入度始終不爲0,最後numCourses > 0,即不能完成拓撲排序返回空。
解法二:
基本思想:深度優先搜索,判斷圖中是否有環的同時拓撲排序保存至res。
計算圖中節點的入度,從入度爲0的節點開始深度優先搜索,搜索到最後一個節點依次遞歸調用節點返回保存至res
藉助一個標誌列表flags,用於判斷每個節點i的狀態:
- 未被 DFS 訪問:i=0;
- 已被其他節點啓動的 DFS 訪問:i=-1;
- 已被當前節點啓動的 DFS 訪問:i=1。
#include<iostream>
#include<vector>
#include<algorithm>
#include<deque>
#include<map>
using namespace std;
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
//基本思想:拓撲排序,給定一個包含n個節點的有向圖G,給出它的節點編號的一種排列,如果滿足:
//對於圖G中的任意一條有向邊(u,v),u在排列中都出現在v的前面,稱該排列是圖G的拓撲排序。
//基本思想:廣度優先搜索
//藉助一個隊列queue,將所有入度爲0的節點入隊
//當queue非空時,依次將隊首節點cur出隊同時加入res,執行numCourses--,並將cur的所有後繼節點的入度 - 1,如果後繼節點的入度爲0則也入隊。
//若整個課程安排圖是有向無環圖,則所有節點一定都入隊並出隊過,最後numCourses = 0,即完成拓撲排序返回res。
//若課程安排圖中存在環路,一定有節點的入度始終不爲0,最後numCourses > 0,即不能完成拓撲排序返回空。
vector<int> res;
deque<int> queue;
multimap<int, int> HashMap;
vector<int> inDegree(numCourses, 0);
int cur;
for (int i = 0; i < prerequisites.size(); i++)
{
inDegree[prerequisites[i][1]]++;
HashMap.insert({ prerequisites[i][0] ,prerequisites[i][1] });
}
for (int i = 0; i < numCourses; i++)
{
if (inDegree[i] == 0)
queue.push_front(i);
}
while (!queue.empty())
{
cur = queue.back();
res.push_back(cur);
queue.pop_back();
numCourses--;
int cnt = HashMap.count(cur);
auto iter = HashMap.find(cur);
while (cnt--)
{
inDegree[iter->second]--;
if (inDegree[iter->second] == 0)
queue.push_front(iter->second);
iter++;
}
}
reverse(res.begin(), res.end());
return (numCourses == 0) ? res : vector<int>{};
}
};
class Solution1 {
public:
multimap<int, int> HashMap; //保存圖中節點關係
vector<int> res; //最後返回的拓撲排序
vector<int> inDegree; //計算節點的入度
vector<int> flags; //標記每個節點i的狀態
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
//基本思想:深度優先搜索,判斷圖中是否有環的同時拓撲排序保存至res。
//計算圖中節點的入度,從入度爲0的節點開始深度優先搜索,搜索到最後一個節點依次遞歸調用節點返回保存至res
//藉助一個標誌列表flags,用於判斷每個節點i的狀態:
//未被 DFS 訪問:i=0;
//已被其他節點啓動的 DFS 訪問:i=-1;
//已被當前節點啓動的 DFS 訪問:i=1。
for (int i = 0; i < numCourses; i++)
{
inDegree.push_back(0);
flags.push_back(0);
}
//計算所有節點的入度,並建立HashMap保存節點關係方便快速查找當前節點的後繼節點
for (int i = 0; i < prerequisites.size(); i++)
{
inDegree[prerequisites[i][1]]++;
HashMap.insert({ prerequisites[i][0],prerequisites[i][1] });
}
for (int i = 0; i < numCourses; i++)
{
if (inDegree[i] == 0)
{
if (dfs(i) == false)
return {};
}
}
return (res.size() == numCourses) ? res : vector<int>();
}
bool dfs(int cur)
{
if (flags[cur] == -1)
return true;
if (flags[cur] == 1)
return false;
flags[cur] = 1;
int cnt = HashMap.count(cur);
auto iter = HashMap.find(cur);
while (cnt--)
{
if (dfs(iter->second) == false)
return false;
iter++;
}
res.push_back(cur);
flags[cur] = -1;
return true;
}
};
int main()
{
Solution1 solute;
int numCourses = 3;
vector<vector<int>> prerequisites = { {1,0},{2,0 } };
vector<int> res = solute.findOrder(numCourses, prerequisites);
for_each(res.begin(), res.end(), [](const auto v) {cout << v << endl; });
return 0;
}