diff --git a/graphs/find_path.py b/graphs/find_path.py index a726052..afa1df2 100644 --- a/graphs/find_path.py +++ b/graphs/find_path.py @@ -1,6 +1,7 @@ """ Functions for finding paths in graphs. """ +from collections import defaultdict def find_path(graph, start, end, path=[]): """ @@ -22,11 +23,14 @@ def find_all_path(graph, start, end, path=[]): Find all paths between two nodes using recursion and backtracking """ path = path + [start] + if start == end: return [path] if not start in graph: return [] + paths = [] + for node in graph[start]: if node not in path: newpaths = find_all_path(graph, node, end, path) @@ -51,3 +55,13 @@ def find_shortest_path(graph, start, end, path=[]): if not shortest or len(newpath) < len(shortest): shortest = newpath return shortest + +if __name__ == '__main__': + edges = [[4, 3, 1], [3, 2, 4], [3], [4], []] + graph = defaultdict(list) + for i, e in enumerate(edges): + for d in e: + graph[i].append(d) + print("The graph is:", graph) + paths = find_all_path(start=0, end=4) + print("All paths are", paths) diff --git a/graphs/lca.py b/graphs/lca.py index 6a23df4..22d2067 100644 --- a/graphs/lca.py +++ b/graphs/lca.py @@ -39,4 +39,55 @@ def __call__(self, a, b): b = self.time[b] if a > b: a, b = b, a - return self.path[self.rmq.query(a, b)] \ No newline at end of file + return self.path[self.rmq.query(a, b)] + +def lca_recursive(self, root, a, b): + # Standard Recipe to find the lowest-common ancestor + if root is None or root is a or root is b: + return root + left = self.lca(root.left, a, b) + right = self.lca(root.right, a, b) + if left is not None and right is not None: + return root + return left if left else right + + +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + stack = [root, ] + parent_map = {root: None} + + while stack: + node = stack.pop() + if node.left: + parent_map[node.left] = node + stack.append(node.left) + if node.right: + parent_map[node.right] = node + stack.append(node.right) + + if p not in parent_map or q not in parent_map: + return None + + ancestors = set() + while p : + ancestors.add(p) + p = parent_map[p] + + while q not in ancestors: + q = parent_map[q] + return q + + +if __name__ == "__main__": + g = {0: [1], 1: [2, 3, 4], 2: [5, 6], 3: [1], 4: [1, 7], 5: [2], 6: [2], 7: [4]} + print("The graph is:", g) + lca = LCA(1, g) + result = lca(5, 6) + print("The lowest common ancestor is:", result) diff --git a/graphs/misc/find_all_paths_from_source_to_target.py b/graphs/misc/find_all_paths_from_source_to_target.py deleted file mode 100755 index 0c26cd5..0000000 --- a/graphs/misc/find_all_paths_from_source_to_target.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Given a graph, find all paths from source to target -""" -from collections import defaultdict - - -def find_all_paths(start, end, path=[]): - """ - Args: - start: Starting destination - end: End Destination - path: Initial path - [] - Returns: returns all_paths, list of lists - """ - # Container to store the result - all_paths = [] - - # This line is creating each path, and start changes during every recursion call - path = path + [start] - # print(f"The all paths are {all_paths}") - # print("The path is: ", path) - - # When you reach the end of one full-path, that is returned - if start == end: - return [path] - - for node in graph[start]: - # print("The node is ", node) - if node not in path: - new_paths = find_all_paths(node, end, path) - # print(f"The new paths is {new_paths}") - for np in new_paths: - all_paths.append(np) - - return all_paths - - -if __name__ == "__main__": - edges = [[4, 3, 1], [3, 2, 4], [3], [4], []] - graph = defaultdict(list) - for i, e in enumerate(edges): - for d in e: - graph[i].append(d) - print("The graph is:", graph) - paths = find_all_paths(start=0, end=4) - print("All paths are", paths) diff --git a/graphs/misc/lca_recursion.py b/graphs/misc/lca_recursion.py deleted file mode 100755 index a1f7eaa..0000000 --- a/graphs/misc/lca_recursion.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Standard recipe to find the lowest common ancestor -""" - - -def lca(self, root, a, b): - # Standard Recipe to find the lowest-common ancestor - if root is None or root is a or root is b: - return root - left = self.lca(root.left, a, b) - right = self.lca(root.right, a, b) - if left is not None and right is not None: - return root - return left if left else right diff --git a/graphs/misc/lca_tree.py b/graphs/misc/lca_tree.py deleted file mode 100644 index b684dab..0000000 --- a/graphs/misc/lca_tree.py +++ /dev/null @@ -1,32 +0,0 @@ -# Definition for a binary tree node. -class TreeNode: - def __init__(self, x): - self.val = x - self.left = None - self.right = None - -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - stack = [root, ] - parent_map = {root: None} - - while stack: - node = stack.pop() - if node.left: - parent_map[node.left] = node - stack.append(node.left) - if node.right: - parent_map[node.right] = node - stack.append(node.right) - - if p not in parent_map or q not in parent_map: - return None - - ancestors = set() - while p : - ancestors.add(p) - p = parent_map[p] - - while q not in ancestors: - q = parent_map[q] - return q \ No newline at end of file diff --git a/graphs/misc/lca_with_pre_processing_seg_tree.py b/graphs/misc/lca_with_pre_processing_seg_tree.py deleted file mode 100644 index 618d198..0000000 --- a/graphs/misc/lca_with_pre_processing_seg_tree.py +++ /dev/null @@ -1,71 +0,0 @@ -class RangeQuery: - def __init__(self, data, func=min): - self.func = func - self._data = _data = [list(data)] - i, n = 1, len(_data[0]) - while 2 * i <= n: - prev = _data[-1] - _data.append([func(prev[j], prev[j + i]) for j in range(n - 2 * i + 1)]) - i <<= 1 - - def query(self, begin, end): - depth = (end - begin).bit_length() - 1 - return self.func( - self._data[depth][begin], self._data[depth][end - (1 << depth)] - ) - - -class LCA: - def __init__(self, root, graph): - """Assumes the graph is zero-indexed""" - - self.first = [-1] * len(graph) - self.path = [-1] * len(graph) - parents = [-1] * len(graph) - h = -1 - - dfs = [root] - - # This is just routine-standard DFS traversal - while dfs: - print("The dfs is:", dfs) - node = dfs.pop() - print("The node popped is:", node) - self.path[h] = parents[node] - self.first[node] = h = h + 1 - for nei in graph[node]: - if self.first[nei] == -1: - parents[nei] = node - dfs.append(nei) - - print("The parents array is:", parents) - print("The first array:", self.first) - print("The path is:", self.path) - print("****************************************************************") - - heights = [self.first[node] for node in self.path] - print("The heights are:", heights) - # Instantiating the rangeQuery class with heights - self.rmq = RangeQuery(heights) - - def __call__(self, left, right): - if left == right: - return left - - # The first array is storing the heights - left = self.first[left] - right = self.first[right] - - # If left is greater than right - if left > right: - left, right = right, left - - return self.path[self.rmq.query(left, right)] - - -if __name__ == "__main__": - g = {0: [1], 1: [2, 3, 4], 2: [5, 6], 3: [1], 4: [1, 7], 5: [2], 6: [2], 7: [4]} - print("The graph is:", g) - lca = LCA(1, g) - result = lca(5, 6) - print("The lowest common ancestor is:", result) diff --git a/graphs/misc/tree_diameter_using_dfs.py b/graphs/misc/tree_diameter_using_dfs.py deleted file mode 100644 index 1a4f1de..0000000 --- a/graphs/misc/tree_diameter_using_dfs.py +++ /dev/null @@ -1,50 +0,0 @@ -from collections import defaultdict, deque - - -def find_tree_diameter(g, n): - """ - Standard awesome problem - So for each node, I want to find the maximum distance to another node - :param g: - :return: - """ - # We can approach this question in the binary tree way (or) the graph way - # Tree - Post order traversal - This in itself is DFS template only - # Graph - Use routine DFS - Remember - tree is an undirected graph - - diameter_of_tree = 0 - - for i in range(1, n + 1): - print(f"Considering {i} as root") - curr_max_length = 0 - q = deque() - q.append((i, 0)) - visited = set() - - while q: - print("The queue is:", q) - node, length = q.pop() - visited.add(node) - curr_max_length = max(length, curr_max_length) - - for nei in g[node]: - if nei not in visited: - q.append((nei, length + 1)) - - print(f"The max_length for {i} is: {curr_max_length}") - diameter_of_tree = max(curr_max_length, diameter_of_tree) - print("*****************************************************") - - return diameter_of_tree - - -if __name__ == "__main__": - n = int(input()) - g = defaultdict(list) - for i in range(0, n - 1): - u, v = map(int, input().split()) - g[u].append(v) - g[v].append(u) - # print(g) - result = find_tree_diameter(g, n) - print(result) diff --git a/graphs/misc/tree_diameter_using_dfs_optimized.py b/graphs/tree_diameter.py similarity index 55% rename from graphs/misc/tree_diameter_using_dfs_optimized.py rename to graphs/tree_diameter.py index 25134a4..24fe54f 100644 --- a/graphs/misc/tree_diameter_using_dfs_optimized.py +++ b/graphs/tree_diameter.py @@ -1,7 +1,44 @@ from collections import defaultdict, deque -def find_tree_diameter(g): +def find_tree_diameter(g, n): + """ + Standard awesome problem + So for each node, I want to find the maximum distance to another node + :param g: + :return: + """ + # We can approach this question in the binary tree way (or) the graph way + # Tree - Post order traversal - This in itself is DFS template only + # Graph - Use routine DFS - Remember - tree is an undirected graph + + diameter_of_tree = 0 + + for i in range(1, n + 1): + print(f"Considering {i} as root") + curr_max_length = 0 + q = deque() + q.append((i, 0)) + visited = set() + + while q: + print("The queue is:", q) + node, length = q.pop() + visited.add(node) + curr_max_length = max(length, curr_max_length) + + for nei in g[node]: + if nei not in visited: + q.append((nei, length + 1)) + + print(f"The max_length for {i} is: {curr_max_length}") + diameter_of_tree = max(curr_max_length, diameter_of_tree) + print("*****************************************************") + + return diameter_of_tree + + +def find_tree_diameter_optimized(g): """ Standard awesome problem So for each node, I want to find the maximum distance to another node @@ -53,7 +90,6 @@ def find_tree_diameter(g): return diameter_of_tree - if __name__ == "__main__": n = int(input()) g = defaultdict(list) @@ -62,5 +98,5 @@ def find_tree_diameter(g): g[u].append(v) g[v].append(u) # print(g) - result = find_tree_diameter(g) + result = find_tree_diameter(g, n) print(result)