2
2
# https://en.wikipedia.org/wiki/Breadth-first_search
3
3
4
4
from __future__ import annotations
5
-
6
5
from queue import Queue
7
6
8
7
9
8
def swap (a : int , b : int ) -> tuple [int , int ]:
10
9
"""
11
- Return a tuple (b, a) when given two integers a and b
12
- >>> swap(2,3)
10
+ Return a tuple (b, a) when given two integers a and b.
11
+
12
+ >>> swap(2, 3)
13
13
(3, 2)
14
- >>> swap(3,4)
14
+ >>> swap(3, 4)
15
15
(4, 3)
16
16
>>> swap(67, 12)
17
17
(12, 67)
@@ -24,22 +24,30 @@ def swap(a: int, b: int) -> tuple[int, int]:
24
24
25
25
def create_sparse (max_node : int , parent : list [list [int ]]) -> list [list [int ]]:
26
26
"""
27
- Create a sparse table which saves each node's 2^i-th parent.
28
-
29
- >>> max_node = 5
30
- >>> parent = [
31
- ... [0, 0, 1, 1, 2, 2], # 2^0-th parents
32
- ... [0, 0, 0, 0, 1, 1] # 2^1-th parents
33
- ... ]
34
- >>> create_sparse(max_node, parent)
35
- [[0, 0, 1, 1, 2, 2], [0, 0, 0, 0, 1, 1]]
36
- >>> max_node = 3
37
- >>> parent = [
38
- ... [0, 0, 1, 1], # 2^0-th parents
39
- ... [0, 0, 0, 0] # 2^1-th parents
40
- ... ]
41
- >>> create_sparse(max_node, parent)
42
- [[0, 0, 1, 1], [0, 0, 0, 0]]
27
+ Create a sparse table that saves each node's 2^i-th parent.
28
+
29
+ The given `parent` table should have the direct parent of each node in row 0.
30
+ The function then fills in parent[j][i] = parent[j-1][parent[j-1][i]] for each j where 2^j < max_node.
31
+
32
+ For example, consider a small tree where:
33
+ - Node 1 is the root (its parent is 0),
34
+ - Nodes 2 and 3 have parent 1.
35
+
36
+ We set up the parent table for only two levels (row 0 and row 1)
37
+ for max_node = 3. (Note that in practice the table has many rows.)
38
+
39
+ >>> # Create an initial parent table with 2 rows and indices 0..3.
40
+ >>> parent0 = [0, 0, 1, 1] # 0 is unused; node1's parent=0, node2 and 3's parent=1.
41
+ >>> parent1 = [0, 0, 0, 0]
42
+ >>> parent = [parent0, parent1]
43
+ >>> # We need at least (1 << j) < max_node holds only for j = 1 here since (1 << 1)=2 < 3 and (1 << 2)=4 !< 3.
44
+ >>> sparse = create_sparse(3, parent)
45
+ >>> sparse[1][1], sparse[1][2], sparse[1][3]
46
+ (0, 0, 0)
47
+ >>> # Explanation:
48
+ >>> # For node 1: parent[1][1] = parent[0][parent[0][1]] = parent[0][0] = 0.
49
+ >>> # For node 2: parent[1][2] = parent[0][parent[0][2]] = parent[0][1] = 0.
50
+ >>> # For node 3: parent[1][3] = parent[0][parent[0][3]] = parent[0][1] = 0.
43
51
"""
44
52
j = 1
45
53
while (1 << j ) < max_node :
@@ -49,69 +57,46 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]:
49
57
return parent
50
58
51
59
52
- # returns lca of node u,v
53
60
def lowest_common_ancestor (
54
61
u : int , v : int , level : list [int ], parent : list [list [int ]]
55
62
) -> int :
56
63
"""
57
- Return the lowest common ancestor of nodes u and v.
58
-
59
- >>> max_node = 13
60
- >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)]
61
- >>> level = [-1 for _ in range(max_node + 10)]
62
- >>> graph = {
63
- ... 1: [2, 3, 4],
64
- ... 2: [5],
65
- ... 3: [6, 7],
66
- ... 4: [8],
67
- ... 5: [9, 10],
68
- ... 6: [11],
69
- ... 7: [],
70
- ... 8: [12, 13],
71
- ... 9: [],
72
- ... 10: [],
73
- ... 11: [],
74
- ... 12: [],
75
- ... 13: [],
76
- ... }
77
- >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1)
78
- >>> parent = create_sparse(max_node, parent)
79
- >>> lowest_common_ancestor(1, 3, level, parent)
80
- 1
81
- >>> lowest_common_ancestor(5, 6, level, parent)
64
+ Return the lowest common ancestor (LCA) of nodes u and v in a tree.
65
+
66
+ The lists `level` and `parent` must be precomputed. `level[i]` is the depth of node i,
67
+ and `parent` is a sparse table where parent[0][i] is the direct parent of node i.
68
+
69
+ >>> # Consider a simple tree:
70
+ >>> # 1
71
+ >>> # / \\
72
+ >>> # 2 3
73
+ >>> # With levels: level[1]=0, level[2]=1, level[3]=1 and parent[0]=[0,0,1,1]
74
+ >>> level = [-1, 0, 1, 1] # index 0 is dummy
75
+ >>> parent = [[0, 0, 1, 1]] + [[0, 0, 0, 0] for _ in range(19)]
76
+ >>> lowest_common_ancestor(2, 3, level, parent)
82
77
1
83
- >>> lowest_common_ancestor(7, 11, level, parent)
84
- 1
85
- >>> lowest_common_ancestor(6, 7, level, parent)
86
- 3
87
- >>> lowest_common_ancestor(4, 12, level, parent)
88
- 4
89
- >>> lowest_common_ancestor(8, 8, level, parent)
90
- 8
91
- >>> lowest_common_ancestor(9, 10, level, parent)
92
- 5
93
- >>> lowest_common_ancestor(12, 13, level, parent)
94
- 8
78
+ >>> # LCA of a node with itself is itself.
79
+ >>> lowest_common_ancestor(2, 2, level, parent)
80
+ 2
95
81
"""
96
- # u must be deeper in the tree than v
82
+ # Ensure u is at least as deep as v.
97
83
if level [u ] < level [v ]:
98
84
u , v = swap (u , v )
99
- # making depth of u same as depth of v
85
+ # Bring u up to the same level as v.
100
86
for i in range (18 , - 1 , - 1 ):
101
87
if level [u ] - (1 << i ) >= level [v ]:
102
88
u = parent [i ][u ]
103
- # at the same depth if u==v that mean lca is found
89
+ # If they are the same, we've found the LCA.
104
90
if u == v :
105
91
return u
106
- # moving both nodes upwards till lca in found
92
+ # Move u and v up together until the LCA is found.
107
93
for i in range (18 , - 1 , - 1 ):
108
94
if parent [i ][u ] not in [0 , parent [i ][v ]]:
109
95
u , v = parent [i ][u ], parent [i ][v ]
110
- # returning longest common ancestor of u,v
96
+ # Return the parent (direct ancestor) which is the LCA.
111
97
return parent [0 ][u ]
112
98
113
99
114
- # runs a breadth first search from root node of the tree
115
100
def breadth_first_search (
116
101
level : list [int ],
117
102
parent : list [list [int ]],
@@ -120,54 +105,23 @@ def breadth_first_search(
120
105
root : int = 1 ,
121
106
) -> tuple [list [int ], list [list [int ]]]:
122
107
"""
123
- Perform a breadth-first search from the root node of the tree.
124
- Sets every node's direct parent and calculates the depth of each node from the root.
125
-
126
- >>> max_node = 5
127
- >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)]
128
- >>> level = [-1 for _ in range(max_node + 10)]
129
- >>> graph = {
130
- ... 1: [2, 3],
131
- ... 2: [4],
132
- ... 3: [5],
133
- ... 4: [],
134
- ... 5: []
135
- ... }
136
- >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1)
137
- >>> level[:6]
138
- [ -1, 0, 1, 1, 2, 2]
139
- >>> parent[0][1] == 0
140
- True
141
- >>> parent[0][2] == 1
142
- True
143
- >>> parent[0][3] == 1
144
- True
145
- >>> parent[0][4] == 2
146
- True
147
- >>> parent[0][5] == 3
148
- True
149
-
150
- >>> # Test with disconnected graph
151
- >>> max_node = 4
152
- >>> parent = [[0 for _ in range(max_node + 10)] for _ in range(20)]
153
- >>> level = [-1 for _ in range(max_node + 10)]
154
- >>> graph = {
155
- ... 1: [2],
156
- ... 2: [],
157
- ... 3: [4],
158
- ... 4: []
159
- ... }
160
- >>> level, parent = breadth_first_search(level, parent, max_node, graph, 1)
161
- >>> level[:5]
162
- [ -1, 0, 1, -1, -1]
163
- >>> parent[0][1] == 0
164
- True
165
- >>> parent[0][2] == 1
166
- True
167
- >>> parent[0][3] == 0
168
- True
169
- >>> parent[0][4] == 3
170
- True
108
+ Run a breadth-first search (BFS) from the root node of the tree.
109
+
110
+ Sets every node's direct parent (in parent[0]) and calculates the depth (level)
111
+ of each node from the root.
112
+
113
+ >>> # Consider a simple tree:
114
+ >>> # 1
115
+ >>> # / \\
116
+ >>> # 2 3
117
+ >>> graph = {1: [2, 3], 2: [], 3: []}
118
+ >>> level = [-1] * 4 # index 0 is unused; nodes 1 to 3.
119
+ >>> parent = [[0] * 4 for _ in range(20)]
120
+ >>> new_level, new_parent = breadth_first_search(level, parent, 3, graph, root=1)
121
+ >>> new_level[1:4]
122
+ [0, 1, 1]
123
+ >>> new_parent[0][1:4]
124
+ [0, 1, 1]
171
125
"""
172
126
level [root ] = 0
173
127
q : Queue [int ] = Queue (maxsize = max_node )
@@ -183,10 +137,46 @@ def breadth_first_search(
183
137
184
138
185
139
def main () -> None :
140
+ """
141
+ Run a BFS to set node depths and parents in a sample tree,
142
+ then create the sparse table and compute several lowest common ancestors.
143
+
144
+ The sample tree used is:
145
+
146
+ 1
147
+ / | \
148
+ 2 3 4
149
+ / / \\ \\
150
+ 5 6 7 8
151
+ / \\ | / \\
152
+ 9 10 11 12 13
153
+
154
+ The expected lowest common ancestors are:
155
+ - LCA(1, 3) --> 1
156
+ - LCA(5, 6) --> 1
157
+ - LCA(7, 11) --> 3
158
+ - LCA(6, 7) --> 3
159
+ - LCA(4, 12) --> 4
160
+ - LCA(8, 8) --> 8
161
+
162
+ To test main() without it printing to the console, we capture the output.
163
+
164
+ >>> import sys
165
+ >>> from io import StringIO
166
+ >>> backup = sys.stdout
167
+ >>> sys.stdout = StringIO()
168
+ >>> main()
169
+ >>> output = sys.stdout.getvalue()
170
+ >>> sys.stdout = backup
171
+ >>> 'LCA of node 1 and 3 is: 1' in output
172
+ True
173
+ >>> 'LCA of node 7 and 11 is: 3' in output
174
+ True
175
+ """
186
176
max_node = 13
187
- # initializing with 0
177
+ # initializing with 0; extra space is allocated.
188
178
parent = [[0 for _ in range (max_node + 10 )] for _ in range (20 )]
189
- # initializing with -1 which means every node is unvisited
179
+ # initializing with -1 which means every node is unvisited.
190
180
level = [- 1 for _ in range (max_node + 10 )]
191
181
graph : dict [int , list [int ]] = {
192
182
1 : [2 , 3 , 4 ],
0 commit comments