AOV网的概念:
AOV网是有向无环图的一种,全称Activity On Vertex Network,顾名思义 活动在顶点的网,使用顶点表示活动,顶点之间的边表示活动的先后顺序,例如<a,b>有一条从a到b的边,表示a活动发生在b之前。
拓扑排序:
在AOV网没有环路的情况下,将这些活动排成一个满足所有边的先后顺序条件线性序列。
排序算法:
1)选择AOV网中一个没有前驱的结点输出;
2)将输出结点从图中删除(并删除与当前结点相连的所有边)
3)重复1)2)直到图中没有结点。
应用:环的判断
对AOV网进行拓扑排序,得到的拓扑有序序列包含所有结点时,证明该AOV网没有环;否则有环。一个例子如下
对于上图情况,由于三个结点都存在自己的前驱,因此不能被访问到,从而证明有环存在。
课程表排课问题:
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例 1:
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法一:使用拓扑排序
该问题说白了就是判断当前图是否有环的问题,我们就可以使用拓扑排序求解。不过并不用真的从图中删除结点和边,只需要维持一个入度数组即可,当一个结点的入度为0时则说明其没有前驱,我们通过这种方式进行拓扑排序。
实现代码如下;
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> adja = new ArrayList<>(numCourses);//临接矩阵
boolean[] access = new boolean[numCourses];
int[] inDegree = new int[numCourses]; // 入度数组
for(int i = 0; i < numCourses; i++){
adja.add(new ArrayList<>());
}
for(int[] p : prerequisites){
for(int i = 1; i < p.length; i++){
adja.get(p[i]).add(p[0]);
inDegree[p[0]]++;
}
}
Queue<Integer> queue = new LinkedList<>();
for(int i = 0; i < numCourses; i++){
if(inDegree[i] == 0){
queue.add(i);
}
}
while(!queue.isEmpty()){
int cur = queue.remove();
if(access[cur]){
continue;
}
access[cur] = true;
for(int next : adja.get(cur)){
if(--inDegree[next] == 0){
queue.add(next);
}
}
}
for(int i = 0; i < numCourses; i++){
if(!access[i]){
return false;
}
}
return true;
}
解法二:
由于该问题可以转化为判断有向图是否有环问题,因此用经典判断图是否有环的三色标记法处理,该方法是对图进行dfs遍历中使用三种颜色标记此时结点的状态,姑且用-1,0,1来表示三种状态,
-1表示当前结点未被访问,
0表示当前结点(及其后继)正在被访问,
1表示当前结点及其后继已经被访问了
因此在dfs遍历过程中访问前若出现当前结点的状态为0时,说明由当前结点的后继可以到达当前结点,如此则证明该有向图是存在环的。实现代码如下:
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> adja = new ArrayList<>(numCourses);
int[] access = new int[numCourses]; // -1 0 1 未访问 -1 正在访问 0 访问完了 1
for(int i = 0; i < numCourses; i++){
adja.add(new ArrayList<>());
access[i] = -1;
}
for(int[] p : prerequisites){
for(int i = 1; i < p.length; i++){
adja.get(p[i]).add(p[0]);
}
}
for(int i = 0; i < numCourses; i++){
if(access[i] == 1){
continue;
}
if(isCicle(i, adja, access)){ //有环则说明无法修完
return false;
}
}
return true;
}
public boolean isCicle(int cur, List<List<Integer>> adja, int[] access){
if(access[cur] == 1){
return false;
}
if(access[cur] == 0){
return true;
}
access[cur] = 0;
boolean result = false;
for(int next : adja.get(cur)){
result = result || isCicle(next, adja, access);
}
access[cur] = 1;
return result;
}