leetCode 207 题,简单易懂
图论算法拓扑排序
网上的答案算的挺快的,这个题想了有 5个小时,幸好是放假,有时间折腾
题目思路
使用深度优先搜索
我们可以看到,第一幅图测试用例是 true,而第二幅图测试用例是false,我们可以建立一个有向图来理解这个题目,只要这个有向图中出现了环,那么就永远没有谁先谁后这个问题了,看过题目的人都知道,比如我要选修两门课程,如果我要修 1课程,那么先修课程是0 必须向学 0 课程 才能学 1号课程
而题目意思也很明显,就是要你推出矛盾,如果你学过图论的话,我们就可以把这个问题转化成 图论算法里面的拓扑排序,具体是什么就不讲了
- 这里有很多解法,我使用的是 dfs
举个例子: 数据告诉我要学算法,首先学数据结构,要学数据结构首先学C,要学C,首先学数学,结果有人说要学数学,首先学数据结构 ,结果你发现重新回到起点了
我们拿到一个数据,首先要建立一个节点之间的关系表, 这个表可以是临接矩阵表示,也可以是一个临接链表,对于稠密图来说最后使用临接矩阵,对于稀疏图来说最好使用临接链表,各有各的优点,这里使用了 临接矩阵的方式 下图代码的 adjacencyTable 二维数组中 ,遍历每个节点,如果,如果两点之间连通,设置为 true,这里其实是有向图,也可以这么做
然后建立一个枚举数组, 枚举类Flag中,visiting表示正在进行dfs操作(会的都懂),visited表示访问过的,一定是有答案的,即无法成环,重点就在这里了,dfs递归的时候如果图没有环的话,到终点就会自然停止,如果这个图有环的话,在递归的时候我会重新回到起点,
代码如下图
// 枚举类,使用 枚举表示节点的状态
enum Flag{
visiting,visited;
}
class Solution {
public boolean canFinish(int c,int[][] course) {
boolean [][] adjacencyTable = new boolean[c][c];
Flag[] flags = new Flag[c];
for(int [] row: course) {
adjacencyTable[row[1]][row[0]] = true;
}
for(int i=0;i<c;i++) {
if(!dfs(adjacencyTable,flags,i)) {
return false;
}
}
return true;
}
private boolean dfs(boolean[][] table,Flag [] flags,int curIndex) {
if(flags[curIndex]==Flag.visiting)
return false;
if(flags[curIndex]==Flag.visited)
return true;
flags[curIndex] = Flag.visiting;
for(int to=0;to<table.length;to++) {
//如果 是环的话,返回 false,表示不行
if(table[curIndex][to]&&!dfs(table,flags,to))
return false;
}
//如果没找到环,就会标记为 visited
flags[curIndex] = Flag.visited;//标记已经访问过了
return true;
}
}
这个思维的难点就是,你在递归下一层的时候你首先额做一个记号 ,表示我正在访问当前点(visiting),如果你在寻路的时候回到了起点,说明了你走进了 一个循环,这时候你如果再继续走的话就会进入一个死循环,因此显式的递归出口是: 当你访问到重复的节点是 立刻返回,因为已经确定了这是一个环 ,而自然结束条件时递归正常终止,这时隐式出口
刚才是用的临接矩阵,以下是临接表
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//建立邻接表,因为题目中说不让用邻接矩阵
LinkedList[] list=new LinkedList[numCourses];//所有节点
for(int i=0;i<numCourses;i++){
list[i]=new LinkedList<Integer>();//每个节点的后续节点
}
for(int i=0;i<prerequisites.length;i++){
//因为数组后面一项依赖前面一项
list[prerequisites[i][1]].add(prerequisites[i][0]);
}
boolean[] visit=new boolean[numCourses];
for(int i=0;i<numCourses;i++){
if(!dfs(list,visit,i)){//遍历i节点的后续节点 dfs为false表示忧患,进入if循环,返回false
return false;
}
}
return true;
}
//深度遍历,如果节点访问过,返回false 表示有环,不能访问
//如果节点未访问过,将visit设置为true
public boolean dfs(LinkedList[] list, boolean[] visit, int num){
if(visit[num]){
return false;
}
else{
visit[num]=true;
}
for(int i=0;i<list[num].size();i++){//后续节点的dfs
if(!dfs(list,visit,(int)list[num].get(i))){//后续节点i已经访问过
return false;
}
list[num].remove(i);
}
visit[num]=false;
return true;
}
}