edge_matrix = [
[ 1, 2 ], # children ids for node 0
[ 2 ], # children ids for node 1
[ ], # children ids for node 2
]
# or use a hash
vertice_children_map = {
0: [1,2],
1: [2]
}
edge_matrix = [
[ 0, 1, 1], # [1,2] are children ids for node 0
[ 0, 0, 1], # [2] are children ids for node 1
[ ], # [] are children ids for node 2
]
4.4.4 用 0 ~ n 的整数代表vertex的id, 用array of pairs代表所有的edges, 每个pair代表指出和指入的vertex id,一般先转换成 4.4.2 基准表述再进行处理。即
vertex_ids = range(0, 3)
edges = [
[0, 1], # node 0 -> node 1
[0, 2], # node 0 -> node 2
[1, 2], # node 1
]
# we need to iterate edges in order to get the children for each node
vertex_children_map = defaultdict(list)
for edge in edges:
vertex_children_map[edge[0]].append(edge[1])
# vertex_children_map will now behave the same as 4.4.2
【TODO】对于问题的潜在解之间存在重复部分的情况,DFS的return值必须体现subproblem的结果,应该将 visited 改成 hash 来实现考虑memoization,也就是 id 作为hash的key,return值也就是子问题的解,作为hash的value。这一点在Chapter 5中会详细描述。
# [Template] (assume we have the standard representation of id => child_ids)
class Graph:
def __init__(self, ids, vertex_children_map):
self.ids = ids
# could be 2d array or hash of arrays
self.vertex_children_map = vertext_children_map
self.visited = set() # OrderedDict or Dict if we need the path
self.visiting = set()
def traverse(self):
try:
for id in self.ids:
self.dfs(id) # or bfs(id)
except Exception:
return # optional
return
"""
DFS
"""
def dfs(self, id: int):
if id in visited: return
if id in visiting:
raise Exception('has cycle')
visiting.add(id)
child_ids = vertext_children_map[id]
for child_id in child_ids:
dfs(child_id)
visiting.remove(id)
visited.add(id) # visited[id] = id for path using OrderedDict
return
"""
BFS without cycle detection
"""
def bfs(self, id: int):
if id in visited: return
queue = [id]
visited.add(id)
while len(queue) != 0:
id = queue.pop(0)
child_ids = vertex_children_map[id]
for child_id in child_ids:
if id not in visited:
# use parent[child_id] = id for path
# use depth[child_id] = depth[child_id] + 1 for depth
visited.add(child_id)
queue.append(child_id)
return
class Solution:
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
self.visited = set()
self.visiting = set() # not entirely necessary, just as demo
self.vertex_children_map = rooms
self.traverse(0)
return len(self.visited) == len(rooms)
def traverse(self, id):
if id in self.visited:
return
if id in self.visiting:
return
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited.add(id)
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
self.ids = range(numCourses)
self.vertex_children_map = defaultdict(list)
for pair in prerequisites:
self.vertex_children_map[pair[1]].append(pair[0])
self.visited = set()
self.visiting = set()
try:
for id in self.ids:
self.traverse(id)
except Exception:
return False
return len(self.visited) == len(self.ids)
def traverse(self, id: int):
if id in self.visited:
return
if id in self.visiting:
raise Exception('has cycle')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited.add(id)
4.9 Topological Sort
# *Kahn's Algorithm
L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edge
while S is not empty do
remove a node x from S
add x to L
for each x's children y do
decrement y's indegree count
if y has 0 indegree then
insert y into S
if graph has edges then
return error (graph has at least one cycle)
else
return L (a topologically sorted order)
# Python Code
class Graph:
def __init__(self, ids, edges: List[List[int]]):
self.ids = ids
# could be 2d array or hash of arrays
self.edges = edges
self.indegrees = Counter()
self.vertex_children_map = defaultdict(list)
for edge in edges:
self.indegrees[edge[1]] += 1
self.vertex_children_map[edge[0]].append(edge[1])
self.visited = []
def traverse(self):
queue = []
for ids in self.ids:
if self.indegrees[id] == 0:
queue.append(id)
while len(queue) != 0:
id = queue.pop(0)
visited.append(id)
child_ids = vertex_children_map[id]
for child_id in child_ids:
indegrees[id] -= 1
if indegrees[id] == 0:
queue.append(id)
return visited if visited.length == self.ids.length else None # has cycle
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
self.ids = range(numCourses)
self.vertex_children_map = defaultdict(list)
for pair in prerequisites:
self.vertex_children_map[pair[1]].append(pair[0])
self.visited = OrderedDict() # change from course schedule I
self.visiting = set()
try:
for id in self.ids:
self.traverse(id)
except Exception:
return [] # change from course schedule I
return reversed(self.visited.keys()) # change from course schedule I
def traverse(self, id: int):
if id in self.visited:
return
if id in self.visiting:
raise Exception('has cycle')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited[id] = id # change from course schedule I
class Solution:
def get_edge_from_words(self, word_a, word_b) -> Optional[List[str]]:
length = min(len(word_a), len(word_b))
for idx in range(length):
if word_a[idx] != word_b[idx]:
return [word_a[idx], word_b[idx]]
if len(word_a) > len(word_b):
raise Exception(
'{} should not appear before {}'.format(word_a, word_b))
return None
def alienOrder(self, words: List[str]) -> str:
self.vertex_children_map = defaultdict(list)
self.ids = set()
for word in words:
self.ids.update(list(word))
try:
for idx in range(1, len(words)):
edge = self.get_edge_from_words(words[idx - 1], words[idx])
if edge:
self.vertex_children_map[edge[0]].append(edge[1])
self.visited = OrderedDict()
self.visiting = set()
for id in self.ids:
self.traverse(id)
except Exception:
return ''
return ''.join(reversed(self.visited.keys()))
def traverse(self, id):
if id in self.visited:
return
if id in self.visiting:
raise Exception('cycle ocurrs')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited[id] = id
e.g.1There are N rooms and you start in room 0. Each room may have some keys to access the next room. Return true if and only if you can enter every room. ()
e.g.2There are a total of numCourses courses you have to take, labeled from 0 to numCourses-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? ( )
e.g.1 There are a total of n courses you have to take labelled from 0 to n - 1. Some courses may have prerequisites, for example, if prerequisites[i] = [ai, bi] this means you must take the course bi before the course ai. Given the total number of courses numCourses and a list of the prerequisite pairs, return the ordering of courses you should take to finish all courses. ( )
e.g.2 There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You receive a list of non-empty words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language. ()