方法一(DFS)
实现思路
将所有的关系抽象为有向图,图的结点代表的是课程,有向边代表的是两者之间的先后关系
核心思想: DFS+访问状态
访问状态的设置:
(1)-1代表没有访问
(2)0代表当前正在访问的结点
(3)1代表已经访问过
大体步骤:
(一)初始化,初始化所有结点的初始访问状态为-1,建立邻接表
(二)遍历所有尚未访问的点进行DFS,此时如果DFS返回false代表有环,否则都遍历完返回true
DFS的核心逻辑
DFS初始时标记当前的结点状态为0,遍历完所有邻居结点后状态设置为-1
在遍历邻居结点的过程中有两种情况返回false
(1)邻居结点状态为0,说明有环
如:0->2,2>0
(2)邻居结点尚未访问,依着该结点往下DFS的时候返回结果为false,说明接下来遍历后出现了环
如:1->2->3->1
实现代码
2021.03.28更新,发现代码的BUG
struct GraphNode{
int val;
vector<int> neighbor;
};
class Solution {
public:
bool DFS(GraphNode*t,vector<int> &vis,vector<GraphNode*> &data){
vis[t->val]=0;
for(int i=0;i<t->neighbor.size();i++){
if(vis[data[t->neighbor[i]]->val]==0){
return false;
}
else if(vis[data[t->neighbor[i]]->val]==-1){
if(DFS(data[t->neighbor[i]],vis,data)==false){
return false;
}
}
}
vis[t->val]=1;//原先错误的写法vis[t->val]=-1;
return true;
}
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//初始化所有访问的结点状态为-1
vector<int> vis(numCourses,-1);
vector<GraphNode*> data(numCourses);
//初始化邻接表
for(int i=0;i<numCourses;i++){
GraphNode *t=new GraphNode;
t->val=i;
data[i]=t;
}
for(auto i:prerequisites){
int x1=i[0];
int x2=i[1];
data[x1]->neighbor.push_back(x2);
}
for(auto item:data){
if(item!=nullptr)
{
if(vis[item->val]==-1&&DFS(item,vis,data)==false){
return false;
}
}
}
for(int i=0;i<data.size();i++){
delete data[i];
}
return true;
}
};
提交结果及分析
2021.03.28修改后的结果:
方法二(BFS)
实现思想
核心思想: BFS+入度
主要思想是结合了宽度搜索和入度的概念,一个结点的入度代表的是它当前所需要依赖的先修课程,当先修课程可以修时被依赖的课程入度就可以减一,当所有结点的入度都为0时说明所有课程都可以修完
实现代码
struct Node{
int val;
vector<Node*> neighbor;
Node(int val){
this->val=val;
}
};
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> gin(numCourses,0);
vector<Node*> graph(numCourses);
for(int i=0;i<numCourses;i++){
graph[i]=new Node(i);
}
for(auto item:prerequisites){
int x1=item[0];
int x2=item[1];
graph[x2]->neighbor.push_back(graph[x1]);
gin[x1]++;
}
for(int j=0;j<numCourses;j++){
for(int i=0;i<numCourses;i++){
if(gin[i]==0){
gin[i]=-1;
for(auto item:graph[i]->neighbor){
gin[item->val]--;
}
}
}
}
for(int i=0;i<numCourses;i++){
// cout<<gin[i]<<endl;
if(gin[i]>0)
return false;
}
return true;
}
};
代码二里面在进行减度操作的时候利用队列将度为0的点压入队列中,从队列中取出点对其临近的点减度操作,发现有新的点的度为0就将其压入队列中,其实这种思想类似于Bellman-floyd改进方法SPFA的思想,只有度变为0才考虑其相邻的结点
提交结果及分析
代码一
时间复杂度O(n^2+m)n为结点个数,m为边的个数
总结
DFS的思想更加抽象,抽离出了问题单纯只考虑有没有环即可
BFS的思想其实跟贴近题目,通俗理解就是能修的课程先修了,每个课程能修的前提考虑它的先修课程都修完了,这里面复习了一个重要入度的概念也要记住