题目链接:
课程表
课程表 II
通过拓扑排序求解, 首先认识有向无环图:
入度表示有多少点指向自己, 出度表示自己指向多少点, 拓扑排序的思想则为选出入度为 0 的点排, 然后将被选出的点指向的点的入度减 1, 当入度被减到 0 时表示该点可以被选出, 一直循环直到全部点被选出或不存在入度为 0 的点.
拓扑排序大致步骤:
1. 建图
可以通过 STL 容器简单建图, 如: vector<vector<int>>, unordered_map<int, vector<int>>, 均表示一个点指向了其他几个点
2. 通过一个一维数组存储各个点的入度, 下标表示该点, 值表示入度
3. 定义一个队列, 将起始入度为 0 的点入队
4. 做一次 bfs, 因为队中的元素都是入度为 0 的点, 所以每次取出对头的点进行拓扑排序, 并同时将被该点指向的点的入度减减, 为 0 时入队, 以此往复直到队列为空
5. 最后判断保存入度的一维数组是否不存在入度大于 0 的点, 如果不存在表示拓扑排序成功, 反之表示存在环
题目中指出: [ai, bi]
,表示在选修课程 ai
前 必须 先选修 bi
表示 bi->ai,根据此规则建图,在建图的过程中同时保存各点的入度,再将入度为 0 的点入队,最后进行一次 bfs 即可,注意两道题非常相像,无非是对入度为 0 的点的处理方式不同,"课程表"这题无需对点进行特殊处理,只需在最后判断是否有环即可,"课程表 II"需要将点添加到一个 vector中,返回拓扑排序后的结果.
"课程表"题解代码:
class Solution
{
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
{
unordered_map<int, vector<int>> edges; //建图
vector<int> in(numCourses); //保存入度
for(const auto& vv : prerequisites)
{
edges[vv[1]].push_back(vv[0]); //注意指向
in[vv[0]]++; //更新入度
}
queue<int> q;
//将当前入度为0的加入到队列
for(int i = 0; i < numCourses; ++i)
{
if(!in[i])
{
q.push(i);
}
}
//bfs
while(!q.empty())
{
int n = q.front();
q.pop();
//更新入度,当经过减减后入度为0则添加到队列
for(const auto& e : edges[n])
{
if(--in[e] == 0)
{
q.push(e);
}
}
}
//判断是否存在环,有环返回false
for(const auto& e : in)
{
if(e)
{
return false;
}
}
return true;
}
};
"课程表 II"题解代码:
class Solution
{
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites)
{
vector<vector<int>> arr(numCourses); //建图
vector<int> in(numCourses); //保存入度
for(const auto& vv : prerequisites)
{
arr[vv[1]].push_back(vv[0]); //注意指向
in[vv[0]]++; //更新入度
}
queue<int> q;
//将当前入度为0的加入到队列
for(int i = 0; i < numCourses; ++i)
{
if(!in[i])
{
q.push(i);
}
}
vector<int> res; //保存拓扑排序后的结果
//bfs
while(!q.empty())
{
int n = q.front();
q.pop();
res.push_back(n);
for(const auto& e : arr[n])
{
if(--in[e] == 0)
{
q.push(e);
}
}
}
//判断是否存在环
for(const auto& e : in)
{
if(e)
{
return {};
}
}
return res;
}
};