课程安排本质上是一道拓扑排序题。
拓扑排序即位找出有向图的顶点的线性排序,使得对于从顶点到顶点的每个有向边,在排序中都在之前。
性质:
当且仅当一个有向图没有环(DAG)才有可能有拓扑排序。
任何DAG具有至少一个拓扑排序,并且已知这些算法用于在线性时间内构建任何DAG的拓扑排序。
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英語:Topological sorting):
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
卡恩算法:
- 假设L是存放结果的列表,先找到那些入度为零的节点,把这些节点放到L中,因为这些节点没有任何的父节点。
- 然后把与这些节点相连的边从图中去掉,同时更新所有节点的入度,再寻找图中的入度为零的节点。
- 对于新找到的这些入度为零的节点来说,他们的父节点已经都在L中了,所以也可以放入L。
- 重复上述操作,直到找不到入度为零的节点。
- 如果此时L中的元素个数和节点总数相同,说明排序完成;如果L中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。
L ← 包含已排序的元素的空列表 S ← 没有进入边的节点(入度为零的节点)的集合 当 S 非空时: 将节点n从S移走 将n加到L尾 for each node m with an edge e from n to m do remove edge e from the graph if m has no other incoming edges then insert m into S 如 图有边 则: return error (图至少有一个环) 否则: return L (以拓扑排序的顺序)
public boolean canFinish(int numCourses, int[][] prerequisites) { //Topological Order //record degree&edges //then remove each node which in-degree is 0 //if the final number of node we've already removed is equal to numCourses, then return true List<Integer>[] adj = new List[numCourses]; for (int i = 0; i < numCourses; i++) adj[i] = new ArrayList<Integer>(); int[] indegree = new int[numCourses]; Queue<Integer> readyCourses = new LinkedList(); int finishCount = 0; for (int i = 0; i < prerequisites.length; i++) { int curCourse = prerequisites[i][0]; int preCourse = prerequisites[i][1]; adj[preCourse].add(curCourse); indegree[curCourse]++; } for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) readyCourses.offer(i); } while (!readyCourses.isEmpty()) { int course = readyCourses.poll(); finishCount++; for (int nextCourse : adj[course]) { indegree[nextCourse]--; if (indegree[nextCourse] == 0) readyCourses.offer(nextCourse); } } return finishCount == numCourses; }
时间复杂度
空间复杂度
留待以后