前言
207 和 210 本质上一个题,就是改变了输出结果而已,我这两个题一个用于学习,一个用于自我检验,看是否真的搞懂了,建议读者也这么尝试。
题目一
思路
图的存储(有向图)
1、根据题意,有一系列的课程以及课程之间的顺序,课程抽象成节点,课程之间的顺序是边,有点集和边集,经典图的问题,所以第一个事情就是图到底该咋存。
2、最常用的方法就是邻接矩阵和邻接表,但是邻接矩阵太废空间,除非图上的边特多以及图不是很大,要不然不用邻接矩阵,我们这里采用邻接表。
3、
拓扑排序
1、定义:拓扑排序是一个节点序列,保证图中任意一条边(u,v),u在排序序列里一定在v前面
2、通过上方定义可知,有拓扑序列,(前提是在图里面)第一题目中必须给出严格的先后关系,只有有了这种严格的先后关系,才有拓扑排序的必要。第二就是不可能有环路,因为一旦有环路(u,v),(v,u)这样,u必须在v前面,v也必须在u前面,矛盾了,所以没有环路。
3、作用:第一拓扑排序可以在严格要求先后顺序的题目中,给出一个不唯一的、并且符合题目先后顺序要求,的一个结果序列。第二,如果得不到拓扑序列,则说明有环路,可以用来判别环路。
4、本题而言,所有课程就是节点,它规定了严格的课程先后关系,并且答案要求我们输出一个符合要求的任意一个序列,所以这里是用拓扑排序。一旦有环路,输出{}即可,拓扑排序也方便我们判断环路是否存在。
拓扑排序实现
DFS
1、每个节点都规定了3种状态,0:等待、1:正在排序、2:完毕
2、一张大的图,其实可以拆分成若干个树的组合,所以我们从任意一个节点开始走,走完他这棵树之后,再挑一个没走过的节点(状态0)继续走它对应的树
3、每开始处理一个点,节点的状态变成1,等待他的全部子节点都完事了,就剩他自己的时候,那么他的状态就是2,已经排序完毕,把自己入栈。
4、针对任意一个节点u,我们按照深度优先走完它某一个子节点对应的分支,再走他的其他子节点,经典DFS,最后回溯到了自己,这个时候他的全部子节点都已经入栈待好了,按照顺序,这个节点u,应该是他所有子节点的父节点,理应在最上面,此时入栈就在最上面,恰好也满足了拓扑排序的需要。
5、当dfs一个新节点(状态是0),就按照深度优先的规则,开一个新函数走新的节点,,一旦碰到状态为1的节点,说明我没等完事呢走回来了,说明有环路,碰到状态为2的节点,此时那个节点已经在答案中了,说明相对位置不会变了,无影响。
BFS
1、有向图,入边=0,说明我前面根本就没人,我肯定是按照要求排在最前面
2、所以找入边是零的,加入队列,每次从队列头那块去一个节点,加入答案,再把该点对应的边全干掉,重新结算所有点的入度,接着把入度为零的点加入队列,直到队列为空
3、最终结果集点的个数少于目标个数,则有环
代码:(DFS+BFS)
DFS
class Solution {
public:
//做图的题之前,一定要事先存储图
vector<vector<int>> edge;
//存储每个节点状态
vector<int> point;
//答案要vector但是我们确实按照栈来存储记录,所以用数组模拟栈
vector<int> ans;
//记录有无环
bool is_cycle = false;
void dfs(int i) {
point[i] = 1;//此时为处理态
for (int e : edge[i]) {
//加强for,找i的所有边的终点
if (point[e] == 0) {
//没走过就走一下
dfs(e);
if (is_cycle) {
//一旦有换,马上快速退出,快速退出通道
return;
}
}
else if (point[e] == 1) {
//说明有环
is_cycle = true;
return;
}
}
point[i] = 2;//所有儿子节点全完事了,加入栈了,则自己也完事了
ans.push_back(i);//把自己加入进去,顺序符合拓扑排序
}
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
edge.resize(10 * numCourses);
point.resize(10 * numCourses);//前提开足空间
for (int i = 0; i < prerequisites.size(); i++) {
//存储图
vector<int> tmp = prerequisites[i];
edge[tmp[1]].push_back(tmp[0]);、
point[i] = 0;//为每一个点标定未访问标签
}
for (int i = 0; i < numCourses; i++) {
if (is_cycle) {
//发现环路,马上退出
break;
}
if (point[i] == 0) {
dfs(i);
}
}
if (is_cycle) {
return {
};
}
reverse(ans.begin(), ans.end());//因为栈的缘故,所以和答案顺序相反
return ans;
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
BFS
class Solution {
public:
vector<vector<int>> edges;
vector<int> point;//存每个点入度
vector<int> ans;
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
edges.resize(10 * numCourses);
point.resize(10 * numCourses);
for (int i = 0; i < prerequisites.size(); i++) {
//存储图
vector<int> tmp = prerequisites[i];
edges[tmp[1]].push_back(tmp[0]);//vector表示的邻接表
point[tmp[0]]++;
}
queue<int> tmp;//临时队列
for (int i = 0; i < numCourses; i++) {
if (point[i] == 0) {
tmp.push(i);
}
}
while (!tmp.empty()) {
int item = tmp.front();
ans.push_back(item);
tmp.pop();
for (int e : edges[item]) {
point[e]--;
if (point[e] == 0) {
tmp.push(e);
}
}
}
if (ans.size() != numCourses) {
return {
};
}
return ans;
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):