def graph_to_adj_matrix(graph: Graph): vertex_count = graph.vertex_len adj_matrix = [([0] * vertex_count) for _ in range(vertex_count)] for u in graph.vertex_keys(): for v, _ in graph.get_vertex(u).successors(): adj_matrix[u][v] = 1 return adj_matrix
def dfs_internal(graph: Graph, src_key, pre_visit_func: Callable[[Any], bool], post_visit_func: Callable[[Any], bool], result: DFSResult, time: TimeCounter) -> bool: if not pre_visit_func(src_key): return False stack = [(src_key, graph.get_vertex(src_key).successors())] result.came_from[src_key] = None result.discover_times[src_key] = time.next() default_next_item = object() while stack: cur_key, iter = stack[-1] next_item = next(iter, default_next_item) if next_item == default_next_item: stack.pop(-1) result.finish_times[cur_key] = time.next() if not post_visit_func(cur_key): return False continue successor_key = next_item[0] if successor_key in result.discover_times: continue if not pre_visit_func(successor_key): return False result.came_from[successor_key] = cur_key result.discover_times[successor_key] = time.next() stack.append( (successor_key, graph.get_vertex(successor_key).successors())) return True
def get_transpose(graph: Graph): """Ex 22.1-3.""" tr = Graph() for v in graph.vertex_keys(): tr.add_vertex(Vertex(v)) for u in graph.vertex_keys(): for v, weight in graph.get_vertex(u).successors(): tr.add_edge(v, u, weight) return tr
def _tree_diameter_recur_internal(tree: Graph, root_key, parents: set) -> Tuple[int, int]: diameter, depth = 0, 0 root = tree.get_vertex(root_key) parents.add(root_key) successor_len = 0 for v_key, _ in root.successors(): if v_key not in parents: successor_len += 1 if successor_len <= 0: # print('root_key=%d, diameter=%d, depth=%d (leaf)' % (root_key, diameter, depth)) return diameter, depth max_sub_diameter, max_sub_depth, second_sub_depth = 0, -1, -1 for v_key, _ in root.successors(): if v_key in parents: continue sub_diameter, sub_depth = _tree_diameter_recur_internal(tree, v_key, parents) max_sub_diameter = max(max_sub_diameter, sub_diameter) if max_sub_depth < 0: max_sub_depth = sub_depth elif sub_depth >= max_sub_depth: second_sub_depth = max_sub_depth max_sub_depth = sub_depth elif sub_depth >= second_sub_depth: second_sub_depth = sub_depth depth = max_sub_depth + 1 diameter = max(max_sub_depth + 1 if second_sub_depth < 0 else max_sub_depth + second_sub_depth + 2, max_sub_diameter) # print('root_key=%d, diameter=%d, depth=%d' % (root_key, diameter, depth)) return diameter, depth
def _check_topological_sort_result(self, graph: Graph, result: Sequence): # result = list(result) self.assertEqual(graph.vertex_len, len(result)) for i in range(len(result) - 1): for j in range(i + 1, len(result)): # print('edge "%s" -> "%s" does not exist.' % (result[j], result[i])) self.assertFalse(graph.has_edge(result[j], result[i]))
def scc_tarjan(graph: Graph) -> List[List]: sccs = [] cache = TarjanCache() time = TimeCounter() for v_key in graph.vertex_keys(): if v_key not in cache.discover_times: _tarjan_internal(graph, v_key, sccs, cache, time) return sccs
def topological_sort_kahn(graph: Graph) -> list: """ Ex 22.4-5. If return list is shorter than graph vertex length, it means the topological sort cannot be performed. O(V + E) time. """ in_degrees = {} ret = [] for v_key in graph.vertex_keys(): in_degrees[v_key] = 0 for u_key in graph.vertex_keys(): for v_key, _ in graph.get_vertex(u_key).successors(): in_degrees[v_key] += 1 zero_in_degree_set = deque() for v_key in graph.vertex_keys(): if in_degrees[v_key] == 0: zero_in_degree_set.append(v_key) while zero_in_degree_set: u_key = zero_in_degree_set.popleft() ret.append(u_key) for v_key, _ in list(graph.get_vertex(u_key).successors()): graph.remove_edge(u_key, v_key) in_degrees[v_key] -= 1 if in_degrees[v_key] == 0: zero_in_degree_set.append(v_key) return ret
def euler_tour_undirected(graph: Graph) -> list: ret = [] if graph.vertex_len == 0: return ret for v_key in graph.vertex_keys(): ret.append(v_key) break ret, _ = _euler(graph, ret, 0) return ret
def _euler(graph: Graph, path: list, src_index: int) -> Tuple[list, int]: src_key = path[src_index] src = graph.get_vertex(src_key) u = src sub_path = [src_key] while u.successor_len > 0: v = None for v_key, _ in u.successors(): v = graph.get_vertex(v_key) break graph.remove_edge(u.key, v.key) graph.remove_edge(v.key, u.key) sub_path.append(v.key) u = v i = 1 while i < len(sub_path): sub_path, delta_len = _euler(graph, sub_path, i) i += delta_len path = path[0:src_index] + sub_path + path[src_index + 1:] return path, len(sub_path)
def test_topological_sort_with_loop(self): cases = [] graph = Graph() graph.add_vertex(Vertex(0)) graph.add_edge(0, 0) cases.append(graph) graph = self._graph_from_figure_22_8() graph.add_edge("v", "p") # Add an edge to form a loop cases.append(graph) for graph in cases: self.assertLess(len(topological_sort_kahn(graph)), graph.vertex_len)
def bfs(graph: Graph, visit_func: Callable[[Any], bool]=None) -> Tuple[dict, dict]: open_set = deque() came_from = {} depths = {} visited = set() # Gray or black nodes if not visit_func: def visit_func(_): return True for v in graph.vertex_keys(): if v in visited: continue _bfs_internal(graph, v, visit_func, open_set, came_from, depths, visited) return came_from, depths
def astar(graph: Graph, src_key, dst_key, heuristic_func: Callable[[Any], float] = None) -> dict: open_set = {src_key: 0} closed_set = set() came_from = {src_key: None} while open_set: u, u_cost = extract_min(open_set, heuristic_func) # Diff from Dijkstra closed_set.add(u) if u == dst_key: # Diff from dijkstra break for v, w in graph.get_vertex(u).successors(): if v in closed_set: continue new_cost = u_cost + graph.edge_weight(u, v) if v not in open_set or open_set[v] > new_cost: open_set[v] = new_cost came_from[v] = u return came_from
def dijkstra(graph: Graph, src_key) -> dict: open_set = {src_key: 0} closed_set = set() came_from = {src_key: None} while open_set: u, u_cost = extract_min(open_set) closed_set.add(u) for v, w in graph.get_vertex(u).successors(): if v in closed_set: continue new_cost = u_cost + w if v not in open_set or open_set[v] > new_cost: came_from[v] = u open_set[v] = new_cost return came_from
def graph_from_adj_matrix(adj_matrix) -> Graph: vertex_count = len(adj_matrix) graph = Graph() for i in range(vertex_count): graph.add_vertex(Vertex(i)) for i in range(vertex_count): for j in range(vertex_count): if adj_matrix[i][j]: graph.add_edge(i, j) return graph
def test_mohammed_scimitars(self): graph = Graph() for i in range(11): graph.add_vertex(Vertex(i)) for edge in ((0, 1), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (3, 5), (3, 6), (4, 5), (4, 8), (5, 6), (5, 8), (6, 7), (6, 10), (7, 8), (7, 9), (7, 10), (8, 9)): graph.add_2_edges(edge[0], edge[1]) self.assertSequenceEqual( [0, 1, 2, 3, 5, 4, 8, 7, 6, 10, 7, 9, 8, 5, 6, 3, 1, 4, 0], euler_tour_undirected(graph))
def _bfs_internal(graph: Graph, src_key, visit_func: Callable[[Any], bool], open_set: deque, came_from: dict, depths: dict, visited: set): open_set.appendleft(src_key) visited.add(src_key) depths[src_key] = 0 while open_set: u = open_set.pop() if not visit_func(u): break for v, _ in graph.get_vertex(u).successors(): if v in visited: continue came_from[v] = u open_set.appendleft(v) visited.add(v) depths[v] = depths[u] + 1
def dfs(graph: Graph, pre_visit_func: Callable[[Any], bool]=None, post_visit_func: Callable[[Any], bool]=None)\ -> DFSResult: """Ex. 22.3-7""" if not pre_visit_func: def pre_visit_func(_): return True if not post_visit_func: def post_visit_func(_): return True result = DFSResult() time = TimeCounter() for v in graph.vertex_keys(): if v not in result.discover_times and not dfs_internal( graph, v, pre_visit_func, post_visit_func, result, time): break return result
def count_simple_path(graph: Graph, src_key, dst_key) -> int: """Ex 22.4-2. O(V + E) time.""" sort_result = topological_sort_dfs(graph) src_index, dst_index = -1, -1 for i in range(len(sort_result)): if sort_result[i] == src_key: src_index = i if sort_result[i] == dst_key: dst_index = i assert src_index >= 0 assert dst_index >= 0 if src_index >= dst_index: return 0 counts = [0] * (dst_index - src_index + 1) checked_vertices = {} for i in range(dst_index - 1, src_index - 1, -1): checked_vertices[sort_result[i]] = i for v_key, _ in graph.get_vertex(sort_result[i]).successors(): if v_key == dst_key: counts[i] += 1 elif v_key in checked_vertices: counts[i] += counts[checked_vertices[v_key]] return counts[0]
def _tarjan_internal(graph: Graph, src_key, sccs: List[List], cache: TarjanCache, time: TimeCounter): cache.in_stack.add(src_key) cache.stack.append(src_key) cache.discover_times[src_key] = cache.low[src_key] = time.next() for v_key, _ in graph.get_vertex(src_key).successors(): if v_key not in cache.discover_times: _tarjan_internal(graph, v_key, sccs, cache, time) cache.low[src_key] = min(cache.low[src_key], cache.low[v_key]) elif v_key not in cache.finish_times: cache.low[src_key] = min(cache.low[src_key], cache.discover_times[v_key]) cache.finish_times[src_key] = time.next() if cache.low[src_key] == cache.discover_times[src_key]: scc = [] while True: v_key = cache.stack.pop() cache.in_stack.remove(v_key) scc.append(v_key) if v_key == src_key: break sccs.append(scc)
def dijkstra_basic_test_case(): graph = Graph() vertices = [Vertex(i) for i in range(1, 8)] for v in vertices: graph.add_vertex(v) graph.add_edge(1, 2, 7) graph.add_edge(2, 1, 7) graph.add_edge(1, 3, 9) graph.add_edge(3, 1, 9) graph.add_edge(1, 6, 14) graph.add_edge(6, 1, 14) graph.add_edge(2, 3, 10) graph.add_edge(3, 2, 10) graph.add_edge(2, 4, 15) graph.add_edge(4, 2, 15) graph.add_edge(3, 4, 11) graph.add_edge(4, 3, 11) graph.add_edge(3, 6, 2) graph.add_edge(6, 3, 2) graph.add_edge(4, 5, 6) graph.add_edge(5, 4, 6) graph.add_edge(5, 6, 9) graph.add_edge(6, 5, 9) src = 1 expected_results = { 1: (1, ), 2: (1, 2), 3: (1, 3), 4: (1, 3, 4), 5: (1, 3, 6, 5), 6: (1, 3, 6), 7: (), } return graph, src, expected_results
def test_euler_undirected(self): graph = Graph() for i in range(7, -1, -1): graph.add_vertex(Vertex(i)) graph.add_2_edges(0, 1) graph.add_2_edges(1, 2) graph.add_2_edges(0, 3) graph.add_2_edges(1, 3) graph.add_2_edges(1, 4) graph.add_2_edges(2, 4) graph.add_2_edges(3, 5) graph.add_2_edges(3, 6) graph.add_2_edges(4, 6) graph.add_2_edges(4, 7) graph.add_2_edges(5, 6) graph.add_2_edges(6, 7) self.assertSequenceEqual([0, 1, 2, 4, 6, 3, 5, 6, 7, 4, 1, 3, 0], euler_tour_undirected(graph)) graph = Graph() for i in range(6): graph.add_vertex(Vertex(i)) graph.add_2_edges(0, 1) graph.add_2_edges(0, 2) graph.add_2_edges(0, 3) graph.add_2_edges(0, 4) graph.add_2_edges(1, 2) graph.add_2_edges(1, 3) graph.add_2_edges(1, 4) graph.add_2_edges(2, 3) graph.add_2_edges(3, 4) graph.add_2_edges(2, 5) graph.add_2_edges(4, 5) self.assertSequenceEqual([0, 1, 2, 3, 4, 5, 2, 0, 3, 1, 4, 0], euler_tour_undirected(graph))
def test_dfs(self): graph = Graph() graph.add_vertex(Vertex('s')) graph.add_vertex(Vertex('t')) graph.add_vertex(Vertex('u')) graph.add_vertex(Vertex('v')) graph.add_vertex(Vertex('w')) graph.add_vertex(Vertex('x')) graph.add_vertex(Vertex('y')) graph.add_vertex(Vertex('z')) graph.add_edge('z', 'y') graph.add_edge('s', 'z') graph.add_edge('y', 'x') graph.add_edge('x', 'z') graph.add_edge('z', 'w') graph.add_edge('s', 'w') graph.add_edge('v', 's') graph.add_edge('t', 'v') graph.add_edge('t', 'u') graph.add_edge('u', 't') graph.add_edge('w', 'x') graph.add_edge('v', 'w') graph.add_edge('u', 'v') pre_visit_array = [] def pre_visit(v): pre_visit_array.append(v) return True post_visit_array = [] def post_visit(v): post_visit_array.append(v) return True result = dfs(graph, pre_visit, post_visit) expected_result = DFSResult() expected_result.discover_times = { 's': 1, 'w': 2, 'x': 3, 'z': 4, 'y': 5, 't': 11, 'u': 12, 'v': 13 } expected_result.finish_times = { 'y': 6, 'z': 7, 'x': 8, 'w': 9, 's': 10, 'v': 14, 'u': 15, 't': 16 } expected_result.came_from = { 's': None, 'w': 's', 'x': 'w', 'z': 'x', 'y': 'z', 't': None, 'u': 't', 'v': 'u' } expected_pre_visit_array = ['s', 'w', 'x', 'z', 'y', 't', 'u', 'v'] expected_post_visit_array = ['y', 'z', 'x', 'w', 's', 'v', 'u', 't'] self.assertEqual(expected_result, result) self.assertSequenceEqual(expected_pre_visit_array, pre_visit_array) self.assertSequenceEqual(expected_post_visit_array, post_visit_array)
def terrain_to_graph(terrain): graph = Graph() if not terrain or not terrain[0]: return graph h = len(terrain) w = len(terrain[0]) for x in range(0, w): for y in range(0, h): graph.add_vertex(Vertex((x, y))) for x in range(0, w): for y in range(0, h): if terrain[y][x] < 0: continue if x - 1 >= 0 and terrain[y][x - 1] >= 0: graph.add_edge((x, y), (x - 1, y), terrain[y][x]) if y - 1 >= 0 and terrain[y - 1][x] >= 0: graph.add_edge((x, y), (x, y - 1), terrain[y][x]) if x + 1 < w and terrain[y][x + 1] >= 0: graph.add_edge((x, y), (x + 1, y), terrain[y][x]) if y + 1 < h and terrain[y + 1][x] >= 0: graph.add_edge((x, y), (x, y + 1), terrain[y][x]) return graph
def _graph_from_figure_22_9() -> Graph: graph = Graph() graph.add_vertex(Vertex('a')) graph.add_vertex(Vertex('b')) graph.add_vertex(Vertex('c')) graph.add_vertex(Vertex('d')) graph.add_vertex(Vertex('e')) graph.add_vertex(Vertex('f')) graph.add_vertex(Vertex('g')) graph.add_vertex(Vertex('h')) graph.add_edge('a', 'b') graph.add_edge('b', 'c') graph.add_edge('b', 'e') graph.add_edge('b', 'f') graph.add_edge('c', 'd') graph.add_edge('c', 'g') graph.add_edge('d', 'c') graph.add_edge('d', 'h') graph.add_edge('e', 'a') graph.add_edge('e', 'f') graph.add_edge('f', 'g') graph.add_edge('g', 'f') graph.add_edge('g', 'h') graph.add_edge('h', 'h') return graph
def _graph_from_figure_22_7(self): graph = Graph() graph.add_vertex(Vertex("undershorts")) graph.add_vertex(Vertex("socks")) graph.add_vertex(Vertex("watch")) graph.add_vertex(Vertex("pants")) graph.add_vertex(Vertex("shoes")) graph.add_vertex(Vertex("belt")) graph.add_vertex(Vertex("shirt")) graph.add_vertex(Vertex("tie")) graph.add_vertex(Vertex("jacket")) graph.add_edge("undershorts", "pants") graph.add_edge("undershorts", "shoes") graph.add_edge("socks", "shoes") graph.add_edge("pants", "shoes") graph.add_edge("pants", "belt") graph.add_edge("shirt", "belt") graph.add_edge("shirt", "tie") graph.add_edge("tie", "jacket") graph.add_edge("belt", "jacket") return graph
def _graph_from_figure_22_8(self): graph = Graph() graph.add_vertex(Vertex("m")) graph.add_vertex(Vertex("n")) graph.add_vertex(Vertex("o")) graph.add_vertex(Vertex("p")) graph.add_vertex(Vertex("q")) graph.add_vertex(Vertex("r")) graph.add_vertex(Vertex("s")) graph.add_vertex(Vertex("t")) graph.add_vertex(Vertex("u")) graph.add_vertex(Vertex("v")) graph.add_vertex(Vertex("w")) graph.add_vertex(Vertex("x")) graph.add_vertex(Vertex("y")) graph.add_vertex(Vertex("z")) graph.add_edge("m", "q") graph.add_edge("m", "r") graph.add_edge("m", "x") graph.add_edge("n", "o") graph.add_edge("n", "q") graph.add_edge("n", "u") graph.add_edge("o", "r") graph.add_edge("o", "s") graph.add_edge("o", "v") graph.add_edge("p", "o") graph.add_edge("p", "s") graph.add_edge("p", "z") graph.add_edge("q", "t") graph.add_edge("r", "u") graph.add_edge("r", "y") graph.add_edge("s", "r") graph.add_edge("u", "t") graph.add_edge("v", "w") graph.add_edge("v", "x") graph.add_edge("w", "z") graph.add_edge("y", "v") return graph
def test_bfs(self): # Figure 22-3 graph = Graph() graph.add_vertex(Vertex('r')) graph.add_vertex(Vertex('s')) graph.add_vertex(Vertex('t')) graph.add_vertex(Vertex('u')) graph.add_vertex(Vertex('v')) graph.add_vertex(Vertex('w')) graph.add_vertex(Vertex('x')) graph.add_vertex(Vertex('y')) graph.add_2_edges('r', 's') graph.add_2_edges('r', 'v') graph.add_2_edges('s', 'w') graph.add_2_edges('t', 'u') graph.add_2_edges('t', 'w') graph.add_2_edges('t', 'x') graph.add_2_edges('u', 'x') graph.add_2_edges('u', 'y') graph.add_2_edges('w', 'x') graph.add_2_edges('x', 'y') came_from = bfs_with_src(graph, 's')[0] self.assertDictEqual({ 'v': 'r', 'r': 's', 'w': 's', 't': 'w', 'x': 'w', 'y': 'x', 'u': 't', }, came_from)
def test_tree_diameter(self): methods = (tree_diameter_recur, tree_diameter_bfs) tree = Graph() for i in range(0, 16): tree.add_vertex(Vertex(i)) tree.add_2_edges(0, 1) tree.add_2_edges(0, 2) tree.add_2_edges(0, 3) tree.add_2_edges(1, 4) tree.add_2_edges(1, 5) tree.add_2_edges(2, 6) tree.add_2_edges(3, 7) tree.add_2_edges(3, 8) tree.add_2_edges(4, 9) tree.add_2_edges(5, 10) tree.add_2_edges(5, 11) tree.add_2_edges(6, 12) tree.add_2_edges(6, 13) tree.add_2_edges(6, 14) tree.add_2_edges(14, 15) for tree_diameter_method in methods: for root in range(0, 16): # print('root: %d' % root) self.assertEqual(7, tree_diameter_method(tree, root))