problem
There are a total of n courses you have to take, labeled from 0 to n -
1.Some courses may have prerequisites, for example to take course 0 you
have to first take course 1, which is expressed as a pair: [0,1]Given the total number of courses and a list of prerequisite pairs, is
it possible for you to finish all courses?For example:
2, [[1,0]] There are a total of 2 courses to take. To take course 1
you should have finished course 0. So it is possible.2, [[1,0],[0,1]] There are a total of 2 courses to take. To take
course 1 you should have finished course 0, and to take course 0 you
should also have finished course 1. So it is impossible.Note: The input prerequisites is a graph represented by a list of
edges, not adjacency matrices. Read more about how a graph is
represented. You may assume that there are no duplicate edges in the
input prerequisites.
分析
这个问题可以抽象为判断一个图中是否有环,可以有两种算法,一种是迭代的删除所有入度为零的节点和它的边,另一种由于深度优先搜索中出现重复节点和有环是等价的,因此我们可以对图进行dfs。
#超过10%
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
g = {i:set() for i in range(numCourses)}
for i, j in prerequisites:
g[i].add(j)
while True:
#所有入度为零的节点
tmp = [i for i in g if not g[i]]
if not tmp:
break
for i in tmp:
#删除节点
del g[i]
for v in g.values():
#删除边
if i in v:
v.remove(i)
return g == {}
树和图都可以使用dict(+set)来实现
DFS
实际上使用DFS不仅可以判断图中是否有环,还可以输出图的拓扑排序(将图转化为一维序列,使得对图中任意边uv,排序时u都出现在v之前)。
图的DFS和树的差不多,只不过在树中不会出现环,但是在图中如果出现环会无限循环,所以必须记录下已访问的节点。
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
g = [[] for _ in range(numCourses)]
for a, b in prerequisites:
g[b].append(a)
visited = [0 for _ in range(numCourses)]
def dfs(node, visited):
visited[node] = 1
for v in g[node]:
#使用for保存接下来要访问的节点
if visited[v] == 1:
return False
elif visited[v] == 0:
if not dfs(v, visited):
return False
visited[node] = 2
return True
for i in range(numCourses):
if visited[i] == 0:
if not dfs(i, visited):
#print visited
return False
return True
visit中的0表示未访问,1表示此次遍历的节点,2表示之前访问的节点,之所以有三种标记是因为防止从中间开始也可以碰到已访问节点,例如2→1→3,从1开始访问,2也会碰到已访问节点,所以使用两种标记不行。
参考文献:
https://www.cs.usfca.edu/~galles/visualization/TopoSortDFS.html
http://www.geeksforgeeks.org/depth-first-traversal-for-a-graph/
http://www.geeksforgeeks.org/topological-sorting/