From 06485a2f014d1d1f9a3cb0fe9a7401a3db79ae10 Mon Sep 17 00:00:00 2001 From: Siddhant Jain <sjain35@buffalo.edu> Date: Mon, 13 Jan 2025 16:41:43 -0500 Subject: [PATCH 1/9] Added doctests to Lowest_common_ancestor.py --- .../binary_tree/lowest_common_ancestor.py | 108 +++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 651037703b95..830f3f85c491 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -24,7 +24,22 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ - creating sparse table which saves each nodes 2^i-th parent + Create a sparse table which saves each node's 2^i-th parent. + + >>> max_node = 5 + >>> parent = [ + ... [0, 0, 1, 1, 2, 2], # 2^0-th parents + ... [0, 0, 0, 0, 1, 1] # 2^1-th parents + ... ] + >>> create_sparse(max_node, parent) + [[0, 0, 1, 1, 2, 2], [0, 0, 0, 0, 1, 1]] + >>> max_node = 3 + >>> parent = [ + ... [0, 0, 1, 1], # 2^0-th parents + ... [0, 0, 0, 0] # 2^1-th parents + ... ] + >>> create_sparse(max_node, parent) + [[0, 0, 1, 1], [0, 0, 0, 0]] """ j = 1 while (1 << j) < max_node: @@ -38,6 +53,46 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: + """ + Return the lowest common ancestor of nodes u and v. + + >>> max_node = 13 + >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] + >>> level = [-1 for _ in range(max_node + 10)] + >>> graph = { + ... 1: [2, 3, 4], + ... 2: [5], + ... 3: [6, 7], + ... 4: [8], + ... 5: [9, 10], + ... 6: [11], + ... 7: [], + ... 8: [12, 13], + ... 9: [], + ... 10: [], + ... 11: [], + ... 12: [], + ... 13: [], + ... } + >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) + >>> parent = create_sparse(max_node, parent) + >>> lowest_common_ancestor(1, 3, level, parent) + 1 + >>> lowest_common_ancestor(5, 6, level, parent) + 1 + >>> lowest_common_ancestor(7, 11, level, parent) + 1 + >>> lowest_common_ancestor(6, 7, level, parent) + 3 + >>> lowest_common_ancestor(4, 12, level, parent) + 4 + >>> lowest_common_ancestor(8, 8, level, parent) + 8 + >>> lowest_common_ancestor(9, 10, level, parent) + 5 + >>> lowest_common_ancestor(12, 13, level, parent) + 8 + """ # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -65,9 +120,54 @@ def breadth_first_search( root: int = 1, ) -> tuple[list[int], list[list[int]]]: """ - sets every nodes direct parent - parent of root node is set to 0 - calculates depth of each node from root node + Perform a breadth-first search from the root node of the tree. + Sets every node's direct parent and calculates the depth of each node from the root. + + >>> max_node = 5 + >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] + >>> level = [-1 for _ in range(max_node + 10)] + >>> graph = { + ... 1: [2, 3], + ... 2: [4], + ... 3: [5], + ... 4: [], + ... 5: [] + ... } + >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) + >>> level[:6] + [ -1, 0, 1, 1, 2, 2] + >>> parent[0][1] == 0 + True + >>> parent[0][2] == 1 + True + >>> parent[0][3] == 1 + True + >>> parent[0][4] == 2 + True + >>> parent[0][5] == 3 + True + + >>> # Test with disconnected graph + >>> max_node = 4 + >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] + >>> level = [-1 for _ in range(max_node + 10)] + >>> graph = { + ... 1: [2], + ... 2: [], + ... 3: [4], + ... 4: [] + ... } + >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) + >>> level[:5] + [ -1, 0, 1, -1, -1] + >>> parent[0][1] == 0 + True + >>> parent[0][2] == 1 + True + >>> parent[0][3] == 0 + True + >>> parent[0][4] == 3 + True """ level[root] = 0 q: Queue[int] = Queue(maxsize=max_node) From b74d7f53925cee24bddd436d6a7d6766d573a17e Mon Sep 17 00:00:00 2001 From: Siddhant Jain <sjain35@buffalo.edu> Date: Mon, 13 Jan 2025 16:56:22 -0500 Subject: [PATCH 2/9] Modified doctest --- .../binary_tree/lowest_common_ancestor.py | 218 +++++++++--------- 1 file changed, 104 insertions(+), 114 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 830f3f85c491..f34e6f7728c8 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -2,16 +2,16 @@ # https://en.wikipedia.org/wiki/Breadth-first_search from __future__ import annotations - from queue import Queue def swap(a: int, b: int) -> tuple[int, int]: """ - Return a tuple (b, a) when given two integers a and b - >>> swap(2,3) + Return a tuple (b, a) when given two integers a and b. + + >>> swap(2, 3) (3, 2) - >>> swap(3,4) + >>> swap(3, 4) (4, 3) >>> swap(67, 12) (12, 67) @@ -24,22 +24,30 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ - Create a sparse table which saves each node's 2^i-th parent. - - >>> max_node = 5 - >>> parent = [ - ... [0, 0, 1, 1, 2, 2], # 2^0-th parents - ... [0, 0, 0, 0, 1, 1] # 2^1-th parents - ... ] - >>> create_sparse(max_node, parent) - [[0, 0, 1, 1, 2, 2], [0, 0, 0, 0, 1, 1]] - >>> max_node = 3 - >>> parent = [ - ... [0, 0, 1, 1], # 2^0-th parents - ... [0, 0, 0, 0] # 2^1-th parents - ... ] - >>> create_sparse(max_node, parent) - [[0, 0, 1, 1], [0, 0, 0, 0]] + Create a sparse table that saves each node's 2^i-th parent. + + The given `parent` table should have the direct parent of each node in row 0. + The function then fills in parent[j][i] = parent[j-1][parent[j-1][i]] for each j where 2^j < max_node. + + For example, consider a small tree where: + - Node 1 is the root (its parent is 0), + - Nodes 2 and 3 have parent 1. + + We set up the parent table for only two levels (row 0 and row 1) + for max_node = 3. (Note that in practice the table has many rows.) + + >>> # Create an initial parent table with 2 rows and indices 0..3. + >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, node2 and 3's parent=1. + >>> parent1 = [0, 0, 0, 0] + >>> parent = [parent0, parent1] + >>> # We need at least (1 << j) < max_node holds only for j = 1 here since (1 << 1)=2 < 3 and (1 << 2)=4 !< 3. + >>> sparse = create_sparse(3, parent) + >>> sparse[1][1], sparse[1][2], sparse[1][3] + (0, 0, 0) + >>> # Explanation: + >>> # For node 1: parent[1][1] = parent[0][parent[0][1]] = parent[0][0] = 0. + >>> # For node 2: parent[1][2] = parent[0][parent[0][2]] = parent[0][1] = 0. + >>> # For node 3: parent[1][3] = parent[0][parent[0][3]] = parent[0][1] = 0. """ j = 1 while (1 << j) < max_node: @@ -49,69 +57,46 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: return parent -# returns lca of node u,v def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: """ - Return the lowest common ancestor of nodes u and v. - - >>> max_node = 13 - >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] - >>> level = [-1 for _ in range(max_node + 10)] - >>> graph = { - ... 1: [2, 3, 4], - ... 2: [5], - ... 3: [6, 7], - ... 4: [8], - ... 5: [9, 10], - ... 6: [11], - ... 7: [], - ... 8: [12, 13], - ... 9: [], - ... 10: [], - ... 11: [], - ... 12: [], - ... 13: [], - ... } - >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) - >>> parent = create_sparse(max_node, parent) - >>> lowest_common_ancestor(1, 3, level, parent) - 1 - >>> lowest_common_ancestor(5, 6, level, parent) + Return the lowest common ancestor (LCA) of nodes u and v in a tree. + + The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i, + and `parent` is a sparse table where parent[0][i] is the direct parent of node i. + + >>> # Consider a simple tree: + >>> # 1 + >>> # / \\ + >>> # 2 3 + >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and parent[0]=[0,0,1,1] + >>> level = [-1, 0, 1, 1] # index 0 is dummy + >>> parent = [[0, 0, 1, 1]] + [[0, 0, 0, 0] for _ in range(19)] + >>> lowest_common_ancestor(2, 3, level, parent) 1 - >>> lowest_common_ancestor(7, 11, level, parent) - 1 - >>> lowest_common_ancestor(6, 7, level, parent) - 3 - >>> lowest_common_ancestor(4, 12, level, parent) - 4 - >>> lowest_common_ancestor(8, 8, level, parent) - 8 - >>> lowest_common_ancestor(9, 10, level, parent) - 5 - >>> lowest_common_ancestor(12, 13, level, parent) - 8 + >>> # LCA of a node with itself is itself. + >>> lowest_common_ancestor(2, 2, level, parent) + 2 """ - # u must be deeper in the tree than v + # Ensure u is at least as deep as v. if level[u] < level[v]: u, v = swap(u, v) - # making depth of u same as depth of v + # Bring u up to the same level as v. for i in range(18, -1, -1): if level[u] - (1 << i) >= level[v]: u = parent[i][u] - # at the same depth if u==v that mean lca is found + # If they are the same, we've found the LCA. if u == v: return u - # moving both nodes upwards till lca in found + # Move u and v up together until the LCA is found. for i in range(18, -1, -1): if parent[i][u] not in [0, parent[i][v]]: u, v = parent[i][u], parent[i][v] - # returning longest common ancestor of u,v + # Return the parent (direct ancestor) which is the LCA. return parent[0][u] -# runs a breadth first search from root node of the tree def breadth_first_search( level: list[int], parent: list[list[int]], @@ -120,54 +105,23 @@ def breadth_first_search( root: int = 1, ) -> tuple[list[int], list[list[int]]]: """ - Perform a breadth-first search from the root node of the tree. - Sets every node's direct parent and calculates the depth of each node from the root. - - >>> max_node = 5 - >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] - >>> level = [-1 for _ in range(max_node + 10)] - >>> graph = { - ... 1: [2, 3], - ... 2: [4], - ... 3: [5], - ... 4: [], - ... 5: [] - ... } - >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) - >>> level[:6] - [ -1, 0, 1, 1, 2, 2] - >>> parent[0][1] == 0 - True - >>> parent[0][2] == 1 - True - >>> parent[0][3] == 1 - True - >>> parent[0][4] == 2 - True - >>> parent[0][5] == 3 - True - - >>> # Test with disconnected graph - >>> max_node = 4 - >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] - >>> level = [-1 for _ in range(max_node + 10)] - >>> graph = { - ... 1: [2], - ... 2: [], - ... 3: [4], - ... 4: [] - ... } - >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1) - >>> level[:5] - [ -1, 0, 1, -1, -1] - >>> parent[0][1] == 0 - True - >>> parent[0][2] == 1 - True - >>> parent[0][3] == 0 - True - >>> parent[0][4] == 3 - True + Run a breadth-first search (BFS) from the root node of the tree. + + Sets every node's direct parent (in parent[0]) and calculates the depth (level) + of each node from the root. + + >>> # Consider a simple tree: + >>> # 1 + >>> # / \\ + >>> # 2 3 + >>> graph = {1: [2, 3], 2: [], 3: []} + >>> level = [-1] * 4 # index 0 is unused; nodes 1 to 3. + >>> parent = [[0] * 4 for _ in range(20)] + >>> new_level, new_parent = breadth_first_search(level, parent, 3, graph, root=1) + >>> new_level[1:4] + [0, 1, 1] + >>> new_parent[0][1:4] + [0, 1, 1] """ level[root] = 0 q: Queue[int] = Queue(maxsize=max_node) @@ -183,10 +137,46 @@ def breadth_first_search( def main() -> None: + """ + Run a BFS to set node depths and parents in a sample tree, + then create the sparse table and compute several lowest common ancestors. + + The sample tree used is: + + 1 + / | \ + 2 3 4 + / / \\ \\ + 5 6 7 8 + / \\ | / \\ + 9 10 11 12 13 + + The expected lowest common ancestors are: + - LCA(1, 3) --> 1 + - LCA(5, 6) --> 1 + - LCA(7, 11) --> 3 + - LCA(6, 7) --> 3 + - LCA(4, 12) --> 4 + - LCA(8, 8) --> 8 + + To test main() without it printing to the console, we capture the output. + + >>> import sys + >>> from io import StringIO + >>> backup = sys.stdout + >>> sys.stdout = StringIO() + >>> main() + >>> output = sys.stdout.getvalue() + >>> sys.stdout = backup + >>> 'LCA of node 1 and 3 is: 1' in output + True + >>> 'LCA of node 7 and 11 is: 3' in output + True + """ max_node = 13 - # initializing with 0 + # initializing with 0; extra space is allocated. parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] - # initializing with -1 which means every node is unvisited + # initializing with -1 which means every node is unvisited. level = [-1 for _ in range(max_node + 10)] graph: dict[int, list[int]] = { 1: [2, 3, 4], From 097e9c6149e80f095be1b3dbef1c04ff94a7325a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:56:50 +0000 Subject: [PATCH 3/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/lowest_common_ancestor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index f34e6f7728c8..33fecb51907b 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -32,7 +32,7 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. - + We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) @@ -65,7 +65,7 @@ def lowest_common_ancestor( The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i, and `parent` is a sparse table where parent[0][i] is the direct parent of node i. - + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -142,9 +142,9 @@ def main() -> None: then create the sparse table and compute several lowest common ancestors. The sample tree used is: - + 1 - / | \ + / | \ 2 3 4 / / \\ \\ 5 6 7 8 @@ -160,7 +160,7 @@ def main() -> None: - LCA(8, 8) --> 8 To test main() without it printing to the console, we capture the output. - + >>> import sys >>> from io import StringIO >>> backup = sys.stdout From d6fff7504f4e99abd6a824f4e8bee13dd0a49772 Mon Sep 17 00:00:00 2001 From: Siddhant Jain <sjain35@buffalo.edu> Date: Mon, 13 Jan 2025 16:59:00 -0500 Subject: [PATCH 4/9] Modified doctest --- .../binary_tree/lowest_common_ancestor.py | 72 +++++++++---------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index f34e6f7728c8..938446946520 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -1,6 +1,3 @@ -# https://en.wikipedia.org/wiki/Lowest_common_ancestor -# https://en.wikipedia.org/wiki/Breadth-first_search - from __future__ import annotations from queue import Queue @@ -25,29 +22,27 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ Create a sparse table that saves each node's 2^i-th parent. - - The given `parent` table should have the direct parent of each node in row 0. - The function then fills in parent[j][i] = parent[j-1][parent[j-1][i]] for each j where 2^j < max_node. - + + The given ``parent`` table should have the direct parent of each node in row 0. + This function fills in: + + parent[j][i] = parent[j - 1][parent[j - 1][i]] + + for each j where 2^j is less than max_node. + For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) - - >>> # Create an initial parent table with 2 rows and indices 0..3. - >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, node2 and 3's parent=1. + + >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, nodes 2 and 3's parent=1. >>> parent1 = [0, 0, 0, 0] >>> parent = [parent0, parent1] - >>> # We need at least (1 << j) < max_node holds only for j = 1 here since (1 << 1)=2 < 3 and (1 << 2)=4 !< 3. >>> sparse = create_sparse(3, parent) - >>> sparse[1][1], sparse[1][2], sparse[1][3] + >>> (sparse[1][1], sparse[1][2], sparse[1][3]) (0, 0, 0) - >>> # Explanation: - >>> # For node 1: parent[1][1] = parent[0][parent[0][1]] = parent[0][0] = 0. - >>> # For node 2: parent[1][2] = parent[0][parent[0][2]] = parent[0][1] = 0. - >>> # For node 3: parent[1][3] = parent[0][parent[0][3]] = parent[0][1] = 0. """ j = 1 while (1 << j) < max_node: @@ -62,20 +57,20 @@ def lowest_common_ancestor( ) -> int: """ Return the lowest common ancestor (LCA) of nodes u and v in a tree. - - The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i, - and `parent` is a sparse table where parent[0][i] is the direct parent of node i. + + The lists ``level`` and ``parent`` must be precomputed. ``level[i]`` is the depth + of node i, and ``parent`` is a sparse table where parent[0][i] is the direct parent + of node i. >>> # Consider a simple tree: >>> # 1 >>> # / \\ >>> # 2 3 - >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and parent[0]=[0,0,1,1] + >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and parent[0]=[0, 0, 1, 1] >>> level = [-1, 0, 1, 1] # index 0 is dummy >>> parent = [[0, 0, 1, 1]] + [[0, 0, 0, 0] for _ in range(19)] >>> lowest_common_ancestor(2, 3, level, parent) 1 - >>> # LCA of a node with itself is itself. >>> lowest_common_ancestor(2, 2, level, parent) 2 """ @@ -93,7 +88,6 @@ def lowest_common_ancestor( for i in range(18, -1, -1): if parent[i][u] not in [0, parent[i][v]]: u, v = parent[i][u], parent[i][v] - # Return the parent (direct ancestor) which is the LCA. return parent[0][u] @@ -106,10 +100,10 @@ def breadth_first_search( ) -> tuple[list[int], list[list[int]]]: """ Run a breadth-first search (BFS) from the root node of the tree. - - Sets every node's direct parent (in parent[0]) and calculates the depth (level) - of each node from the root. - + + This sets each node's direct parent (stored in parent[0]) and calculates the + depth (level) of each node from the root. + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -138,19 +132,19 @@ def breadth_first_search( def main() -> None: """ - Run a BFS to set node depths and parents in a sample tree, - then create the sparse table and compute several lowest common ancestors. - + Run a BFS to set node depths and parents in a sample tree, then create the + sparse table and compute several lowest common ancestors. + The sample tree used is: - 1 - / | \ - 2 3 4 - / / \\ \\ - 5 6 7 8 - / \\ | / \\ - 9 10 11 12 13 - + 1 + / | \ + 2 3 4 + / / \\ \\ + 5 6 7 8 + / \\ | / \\ + 9 10 11 12 13 + The expected lowest common ancestors are: - LCA(1, 3) --> 1 - LCA(5, 6) --> 1 @@ -158,7 +152,7 @@ def main() -> None: - LCA(6, 7) --> 3 - LCA(4, 12) --> 4 - LCA(8, 8) --> 8 - + To test main() without it printing to the console, we capture the output. >>> import sys @@ -174,9 +168,7 @@ def main() -> None: True """ max_node = 13 - # initializing with 0; extra space is allocated. parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] - # initializing with -1 which means every node is unvisited. level = [-1 for _ in range(max_node + 10)] graph: dict[int, list[int]] = { 1: [2, 3, 4], From 18eed567e651b06887eb0bba2a93d1f61380a60d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:02:43 +0000 Subject: [PATCH 5/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../binary_tree/lowest_common_ancestor.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 2a729414da54..6ba53b0af98f 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -22,21 +22,21 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ Create a sparse table that saves each node's 2^i-th parent. - + The given ``parent`` table should have the direct parent of each node in row 0. This function fills in: - + parent[j][i] = parent[j - 1][parent[j - 1][i]] - + for each j where 2^j is less than max_node. - + For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) - + >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, nodes 2 and 3's parent=1. >>> parent1 = [0, 0, 0, 0] >>> parent = [parent0, parent1] @@ -58,11 +58,11 @@ def lowest_common_ancestor( """ Return the lowest common ancestor (LCA) of nodes u and v in a tree. <<<<<<< HEAD - + The lists ``level`` and ``parent`` must be precomputed. ``level[i]`` is the depth of node i, and ``parent`` is a sparse table where parent[0][i] is the direct parent of node i. - + ======= The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i, @@ -107,10 +107,10 @@ def breadth_first_search( ) -> tuple[list[int], list[list[int]]]: """ Run a breadth-first search (BFS) from the root node of the tree. - + This sets each node's direct parent (stored in parent[0]) and calculates the depth (level) of each node from the root. - + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -141,18 +141,18 @@ def main() -> None: """ Run a BFS to set node depths and parents in a sample tree, then create the sparse table and compute several lowest common ancestors. - + The sample tree used is: <<<<<<< HEAD - + 1 - / | \ + / | \ 2 3 4 / / \\ \\ 5 6 7 8 / \\ | / \\ 9 10 11 12 13 - + ======= 1 @@ -171,7 +171,7 @@ def main() -> None: - LCA(6, 7) --> 3 - LCA(4, 12) --> 4 - LCA(8, 8) --> 8 - + To test main() without it printing to the console, we capture the output. >>> import sys From 68cd62c1be72654f586613406e4a7cc54ffee9b5 Mon Sep 17 00:00:00 2001 From: Siddhant Jain <sjain35@buffalo.edu> Date: Mon, 13 Jan 2025 17:06:15 -0500 Subject: [PATCH 6/9] modified --- .../binary_tree/lowest_common_ancestor.py | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 2a729414da54..f99b6c721cf2 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -1,14 +1,17 @@ +# https://en.wikipedia.org/wiki/Lowest_common_ancestor +# https://en.wikipedia.org/wiki/Breadth-first_search + from __future__ import annotations + from queue import Queue def swap(a: int, b: int) -> tuple[int, int]: """ - Return a tuple (b, a) when given two integers a and b. - - >>> swap(2, 3) + Return a tuple (b, a) when given two integers a and b + >>> swap(2,3) (3, 2) - >>> swap(3, 4) + >>> swap(3,4) (4, 3) >>> swap(67, 12) (12, 67) @@ -33,7 +36,7 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. - + We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) @@ -52,23 +55,17 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: return parent +# returns lca of node u,v def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: """ Return the lowest common ancestor (LCA) of nodes u and v in a tree. -<<<<<<< HEAD The lists ``level`` and ``parent`` must be precomputed. ``level[i]`` is the depth of node i, and ``parent`` is a sparse table where parent[0][i] is the direct parent of node i. -======= - - The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i, - and `parent` is a sparse table where parent[0][i] is the direct parent of node i. - ->>>>>>> 097e9c6149e80f095be1b3dbef1c04ff94a7325a >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -81,23 +78,25 @@ def lowest_common_ancestor( >>> lowest_common_ancestor(2, 2, level, parent) 2 """ - # Ensure u is at least as deep as v. + # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) - # Bring u up to the same level as v. + # making depth of u same as depth of v for i in range(18, -1, -1): if level[u] - (1 << i) >= level[v]: u = parent[i][u] - # If they are the same, we've found the LCA. + # at the same depth if u==v that mean lca is found if u == v: return u - # Move u and v up together until the LCA is found. + # moving both nodes upwards till lca in found for i in range(18, -1, -1): if parent[i][u] not in [0, parent[i][v]]: u, v = parent[i][u], parent[i][v] + # returning longest common ancestor of u,v return parent[0][u] +# runs a breadth first search from root node of the tree def breadth_first_search( level: list[int], parent: list[list[int]], @@ -143,7 +142,6 @@ def main() -> None: sparse table and compute several lowest common ancestors. The sample tree used is: -<<<<<<< HEAD 1 / | \ @@ -153,17 +151,6 @@ def main() -> None: / \\ | / \\ 9 10 11 12 13 -======= - - 1 - / | \ - 2 3 4 - / / \\ \\ - 5 6 7 8 - / \\ | / \\ - 9 10 11 12 13 - ->>>>>>> 097e9c6149e80f095be1b3dbef1c04ff94a7325a The expected lowest common ancestors are: - LCA(1, 3) --> 1 - LCA(5, 6) --> 1 @@ -173,7 +160,7 @@ def main() -> None: - LCA(8, 8) --> 8 To test main() without it printing to the console, we capture the output. - + >>> import sys >>> from io import StringIO >>> backup = sys.stdout @@ -187,7 +174,9 @@ def main() -> None: True """ max_node = 13 + # initializing with 0 parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] + # initializing with -1 which means every node is unvisited level = [-1 for _ in range(max_node + 10)] graph: dict[int, list[int]] = { 1: [2, 3, 4], From a82ab0900401ff0a3c616dcd260c6b855d27b7eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:08:27 +0000 Subject: [PATCH 7/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../binary_tree/lowest_common_ancestor.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index f99b6c721cf2..2ca489b1f2a8 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -25,21 +25,21 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ Create a sparse table that saves each node's 2^i-th parent. - + The given ``parent`` table should have the direct parent of each node in row 0. This function fills in: - + parent[j][i] = parent[j - 1][parent[j - 1][i]] - + for each j where 2^j is less than max_node. - + For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. - + We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) - + >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, nodes 2 and 3's parent=1. >>> parent1 = [0, 0, 0, 0] >>> parent = [parent0, parent1] @@ -61,11 +61,11 @@ def lowest_common_ancestor( ) -> int: """ Return the lowest common ancestor (LCA) of nodes u and v in a tree. - + The lists ``level`` and ``parent`` must be precomputed. ``level[i]`` is the depth of node i, and ``parent`` is a sparse table where parent[0][i] is the direct parent of node i. - + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -106,10 +106,10 @@ def breadth_first_search( ) -> tuple[list[int], list[list[int]]]: """ Run a breadth-first search (BFS) from the root node of the tree. - + This sets each node's direct parent (stored in parent[0]) and calculates the depth (level) of each node from the root. - + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -140,17 +140,17 @@ def main() -> None: """ Run a BFS to set node depths and parents in a sample tree, then create the sparse table and compute several lowest common ancestors. - + The sample tree used is: - + 1 - / | \ + / | \ 2 3 4 / / \\ \\ 5 6 7 8 / \\ | / \\ 9 10 11 12 13 - + The expected lowest common ancestors are: - LCA(1, 3) --> 1 - LCA(5, 6) --> 1 @@ -158,9 +158,9 @@ def main() -> None: - LCA(6, 7) --> 3 - LCA(4, 12) --> 4 - LCA(8, 8) --> 8 - + To test main() without it printing to the console, we capture the output. - + >>> import sys >>> from io import StringIO >>> backup = sys.stdout From 0cd031a685b2c63d31775b9656f0b8ecf61ac6e3 Mon Sep 17 00:00:00 2001 From: Siddhant Jain <sjain35@buffalo.edu> Date: Mon, 13 Jan 2025 17:21:30 -0500 Subject: [PATCH 8/9] modify doctest --- .../binary_tree/lowest_common_ancestor.py | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index f99b6c721cf2..8be08ac44f3f 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -23,24 +23,24 @@ def swap(a: int, b: int) -> tuple[int, int]: def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: - """ + r""" Create a sparse table that saves each node's 2^i-th parent. - - The given ``parent`` table should have the direct parent of each node in row 0. - This function fills in: - + + The given ``parent`` table should have the direct parent of each node + in row 0. This function fills in: + parent[j][i] = parent[j - 1][parent[j - 1][i]] - + for each j where 2^j is less than max_node. - + For example, consider a small tree where: - Node 1 is the root (its parent is 0), - Nodes 2 and 3 have parent 1. - + We set up the parent table for only two levels (row 0 and row 1) for max_node = 3. (Note that in practice the table has many rows.) - - >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, nodes 2 and 3's parent=1. + + >>> parent0 = [0, 0, 1, 1] >>> parent1 = [0, 0, 0, 0] >>> parent = [parent0, parent1] >>> sparse = create_sparse(3, parent) @@ -59,18 +59,17 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: def lowest_common_ancestor( u: int, v: int, level: list[int], parent: list[list[int]] ) -> int: - """ + r""" Return the lowest common ancestor (LCA) of nodes u and v in a tree. - - The lists ``level`` and ``parent`` must be precomputed. ``level[i]`` is the depth - of node i, and ``parent`` is a sparse table where parent[0][i] is the direct parent - of node i. - + + The lists ``level`` and ``parent`` must be precomputed. + >>> # Consider a simple tree: >>> # 1 >>> # / \\ >>> # 2 3 - >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and parent[0]=[0, 0, 1, 1] + >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and + >>> # parent[0]=[0, 0, 1, 1] >>> level = [-1, 0, 1, 1] # index 0 is dummy >>> parent = [[0, 0, 1, 1]] + [[0, 0, 0, 0] for _ in range(19)] >>> lowest_common_ancestor(2, 3, level, parent) @@ -104,12 +103,12 @@ def breadth_first_search( graph: dict[int, list[int]], root: int = 1, ) -> tuple[list[int], list[list[int]]]: - """ + r""" Run a breadth-first search (BFS) from the root node of the tree. - + This sets each node's direct parent (stored in parent[0]) and calculates the depth (level) of each node from the root. - + >>> # Consider a simple tree: >>> # 1 >>> # / \\ @@ -117,7 +116,7 @@ def breadth_first_search( >>> graph = {1: [2, 3], 2: [], 3: []} >>> level = [-1] * 4 # index 0 is unused; nodes 1 to 3. >>> parent = [[0] * 4 for _ in range(20)] - >>> new_level, new_parent = breadth_first_search(level, parent, 3, graph, root=1) + >>> new_level, new_parent=breadth_first_search(level,parent,3,graph,root=1) >>> new_level[1:4] [0, 1, 1] >>> new_parent[0][1:4] @@ -137,12 +136,12 @@ def breadth_first_search( def main() -> None: - """ + r""" Run a BFS to set node depths and parents in a sample tree, then create the sparse table and compute several lowest common ancestors. - + The sample tree used is: - + 1 / | \ 2 3 4 @@ -150,7 +149,7 @@ def main() -> None: 5 6 7 8 / \\ | / \\ 9 10 11 12 13 - + The expected lowest common ancestors are: - LCA(1, 3) --> 1 - LCA(5, 6) --> 1 @@ -158,9 +157,9 @@ def main() -> None: - LCA(6, 7) --> 3 - LCA(4, 12) --> 4 - LCA(8, 8) --> 8 - + To test main() without it printing to the console, we capture the output. - + >>> import sys >>> from io import StringIO >>> backup = sys.stdout From 51f78ae79e135d5195eaccacd78136128871b99c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:23:02 +0000 Subject: [PATCH 9/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/lowest_common_ancestor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 8be08ac44f3f..c5b3ff3c26f7 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -143,7 +143,7 @@ def main() -> None: The sample tree used is: 1 - / | \ + / | \ 2 3 4 / / \\ \\ 5 6 7 8