拓扑排序,感觉就是图的广度优先搜索和深度优先搜索。
拓扑排序的两种实现方法:Kahn算法与DFS深度优先搜索算法
Kahn算法:使用的是贪心算法思想,借助一个队列,以及一个统计当前顶点入度数的数组。当顶点的入度为0,那么就把顶点入队列。当队列不为空,那么就取出队列的顶点,同时把该顶点所有指向的顶点取出来,同时把这些顶点的入度都减一,然后判断这些顶点的入度是否为0,如果为0,就将该顶点入队列,不断取该队列的顶点,直到顶点为空。
DFS算法:遍历图的所有顶点,取出一个顶点之后,继续取出该顶点指向的所有顶点。
以下是leetcode第207题的拓扑排序代码,使用了Kahn算法和深度优先算法两种方式解决,其中我感觉Kahn算法比较好理解,但是深度优先的时候,涉及递归,有点被绕晕了,调试了好几个小时,最后瞄了大神们的题解,才缝缝补补的敲出来了。其实就是有一个地方被卡住了,需要借助一个数组保存当前顶点访问状态,在访问某个顶点时,设为1,当其他顶点访问到该顶点为1,表示又回到了该顶点,那么说明有环,否则将该顶点设为-1,表示该顶点已经被访问过了。
package com.freshbin.dataStructAndAlgo.chapter43.mycode;
import java.util.LinkedList;
/**
* 拓扑排序
* @author freshbin
* @date 2020/4/29 9:11
*/
public class Solution {
/**
* leetcode207
* 你这个学期必须选修 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。这是不可能的。
*
* 思路:
* 1、先把所有的课程关联关系都用邻接表存起来
* 2、统计所有的课程数(用一个数组存各个课程的入度数,即先于该课程学习的课程数, 当入度数为0表示不需要依赖课程,直接打印)
* 3、如果统计出来的课程数小于实际课程数,说明有环,否则,无环。
*
* 广度优先算法
* @param numCourses
* @param prerequisites
* @return
*/
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] relyCourseNums = new int[numCourses];
LinkedList<Integer>[] adj = initGraph(numCourses, prerequisites, relyCourseNums);
LinkedList<Integer> queue = new LinkedList<>();
for(int i = 0; i < numCourses; i++) {
if(relyCourseNums[i] == 0) {
queue.add(i); // 入度为0的课程不需要依赖其他课程,可以直接放入队列
}
}
System.out.println("广度优先访问如下");
int count = 0;
while(!queue.isEmpty()) {
int currentCourse = queue.remove();
for(int j = 0; j < adj[currentCourse].size(); j++) {
int nextCourse = adj[currentCourse].get(j);
relyCourseNums[nextCourse]--;
if(relyCourseNums[nextCourse] != 0) {
// 入度不为0,说明nextCourse还存在依赖的课程,即需要先学习的课程,所以这里直接跳过
continue;
}
queue.add(nextCourse);
}
count++;
System.out.print(currentCourse + "->");
}
System.out.println();
return count == numCourses;
}
public LinkedList<Integer>[] initGraph(int n, int[][] prerequisites, int[] relyCourseNums) {
LinkedList<Integer>[] adj = new LinkedList[n];
for(int i = 0; i < n; i++) {
adj[i] = new LinkedList<>();
}
for(int i = 0; i < prerequisites.length; i++) {
int s = prerequisites[i][1];
int t = prerequisites[i][0];
adj[s].add(t); // 构造图
relyCourseNums[t]++; // 统计每个课程需要依赖的课程数
}
return adj;
}
public static void main(String[] arg) {
Solution solution = new Solution();
int numCourse = 2;
// int[][] prerequisites = {{0,1}, {3,1}, {1,3}, {3,2}};
int[][] prerequisites = {{0, 1}};
System.out.println("广度优先,该课程学习路径安排是否合理:" + solution.canFinish(numCourse, prerequisites));
System.out.println();
System.out.println("深度优先,该课程学习路径安排是否合理:" + solution.canFinish01(numCourse, prerequisites));
}
public Boolean flag = true;
/**
* 深度优先算法
* @param numCourses
* @param prerequisites
* @return
*/
public boolean canFinish01(int numCourses, int[][] prerequisites) {
LinkedList<Integer>[] adj = initGraph(numCourses, prerequisites);
LinkedList<Integer>[] inverseAdj = initInverseLinkedList(adj);
System.out.println("深度优先访问如下");
int[] visitedPoint = new int[numCourses];
for(int i = 0; i < numCourses; i++) {
if(!flag) {
return false;
}
if(visitedPoint[i] == -1) {
// 已经被访问过
continue;
}
dfs(i, inverseAdj, visitedPoint);
}
System.out.println();
return flag;
}
public void dfs(int index, LinkedList<Integer>[] inverseAdj, int[] visitedPoint) {
if(visitedPoint[index] == 1) {
flag = false;
return;
}
if(visitedPoint[index] == -1) {
return;
}
visitedPoint[index] = 1;
for(int i = 0; i < inverseAdj[index].size(); i++) {
int courseIndex = inverseAdj[index].get(i);
dfs(courseIndex, inverseAdj, visitedPoint);
}
visitedPoint[index] = -1;
System.out.print(index + "->");
}
public LinkedList<Integer>[] initGraph(int n, int[][] prerequisites) {
LinkedList<Integer>[] adj = new LinkedList[n];
for(int i = 0; i < n; i++) {
adj[i] = new LinkedList<>();
}
for(int i = 0; i < prerequisites.length; i++) {
int s = prerequisites[i][1];
int t = prerequisites[i][0];
adj[s].add(t); // 构造图
}
return adj;
}
/**
* 构造反向边
* @param adj
* @return
*/
public LinkedList<Integer>[] initInverseLinkedList(LinkedList<Integer>[] adj) {
LinkedList<Integer>[] inverseAdj = new LinkedList[adj.length];
for(int i = 0; i < adj.length; i++) {
inverseAdj[i] = new LinkedList<>();
}
for(int i = 0; i < adj.length; i++) {
for(int j = 0; j < adj[i].size(); j++) {
int nextPoint = adj[i].get(j);
inverseAdj[nextPoint].add(i);
}
}
return inverseAdj;
}
}