1- """Create and operate on Binary Trees"""
2-
31# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/Data Structures/Binary Trees/binary_trees.ipynb.
42
53# %% auto 0
64__all__ = ['visualize_tree' , 'Node' , 'set_parents' , 'create_n_nodes' , 'dfs_inorder' , 'dfs_preorder' , 'dfs_postorder' , 'bfs' ,
7- 'insert_node' , 'search_tree' , 'delete_node' ]
5+ 'insert_node' , 'search_tree' , 'delete_node' , 'bst_insert' , 'bst_search' ]
86
97# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 3
108from collections import deque
119import matplotlib .pyplot as plt
1210import networkx as nx
1311from typing import Callable , Generator
1412
15- # %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 5
16- def visualize_tree (root ):
13+ # %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 12
14+ def visualize_tree (root , show_parents = False ):
15+ """Visualize a binary tree using NetworkX and Matplotlib.
16+ Args:
17+ root: The root node of the tree.
18+ show_parents: If True, include parent values in node labels.
19+ """
1720 if not root :
1821 print ("Empty tree" )
1922 return
20-
23+ import math
2124 G = nx .DiGraph ()
2225 pos = {}
2326 labels = {}
2427 node_id = 0
25-
28+ node_map = {}
2629 def add_nodes (node , x = 0.0 , y = 0.0 , layer = 1 ):
2730 nonlocal node_id
2831 if node :
2932 current_id = node_id
33+ node_map [node ] = current_id
3034 node_id += 1
3135 G .add_node (current_id )
3236 pos [current_id ] = (x , y )
33- labels [current_id ] = str (node .value )
34-
37+ if show_parents and hasattr (node , 'parent' ) and node .parent :
38+ labels [current_id ] = f"{ node .value } \n (p:{ node .parent .value } )"
39+ else :
40+ labels [current_id ] = str (node .value )
3541 if node .left :
3642 left_id = node_id
3743 G .add_edge (current_id , left_id )
38- add_nodes (node .left , x - 1 / layer , y - 1 , layer + 1 )
44+ add_nodes (node .left , x - 1 / ( layer + 0.5 ) , y - 1 , layer + 1 )
3945 if node .right :
4046 right_id = node_id
4147 G .add_edge (current_id , right_id )
42- add_nodes (node .right , x + 1 / layer , y - 1 , layer + 1 )
43-
48+ add_nodes (node .right , x + 1 / (layer + 0.5 ), y - 1 , layer + 1 )
4449 return current_id
45-
4650 add_nodes (root )
47-
48- plt .figure (figsize = (3 , 2 ))
49- nx .draw (G , pos , labels = labels , with_labels = True , node_color = 'lightblue' ,
50- node_size = 1000 , font_size = 16 , font_weight = 'bold' , arrows = True )
51+ # Adaptive figure size
52+ depth = root .height if hasattr (root , 'height' ) else 3
53+ width = max (6 , 2 ** (depth - 1 ))
54+ plt .figure (figsize = (width , depth * 2 ))
55+ # Highlight root
56+ node_colors = ["#ffcc00" if n == 0 else "#90caf9" for n in G .nodes ]
57+ nx .draw (G , pos , labels = labels , with_labels = True , node_color = node_colors ,
58+ node_size = 1200 , font_size = 14 , font_weight = 'bold' , arrows = True , edge_color = "#888888" )
5159 plt .title ("Binary Tree Visualization" )
60+ plt .axis ('off' )
5261 plt .show ()
5362
5463# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 14
5564class Node :
56- """A node in a binary tree."""
57- def __init__ (self , key , left : "Node|None" = None , right : "Node|None" = None , parent : "Node|None" = None ):
58- self .value = key
65+ """A node in a binary tree with parent pointer."""
66+ __slots__ = ['value' , 'parent' , 'left' , 'right' ]
67+
68+ def __init__ (self , value , left : "Node|None" = None , right : "Node|None" = None , parent : "Node|None" = None ):
69+ self .value = value
5970 self .parent : Node | None = parent
6071 self .left : Node | None = left
6172 self .right : Node | None = right
6273
6374 def __str__ (self ) -> str :
64- """A string representation of the node including its value, left, and right children ."""
75+ """A string representation of the node including its value, left, right, and parent ."""
6576 left_value = f"Node({ self .left .value } )" if self .left else None
6677 right_value = f"Node({ self .right .value } )" if self .right else None
6778 parent_value = f"Node({ self .parent .value } )" if self .parent else None
6879 return f"Node({ self .value } , left={ left_value } , right={ right_value } , parent={ parent_value } )"
6980
7081 def __repr__ (self ) -> str :
71- """A representation of the node including its value, left, and right children."""
7282 return self .__str__ ()
7383
7484 @property
7585 def height (self ) -> int :
86+ """Returns the height of the node (longest path to a leaf)."""
7687 return 1 + max (
7788 (self .left .height if self .left else 0 ), (self .right .height if self .right else 0 )
7889 )
7990
8091 @property
8192 def depth (self ) -> int :
82- return 1 + (self .parent .depth if self .parent else 0 )
83-
93+ return 0 if self .parent is None else 1 + self .parent .depth
8494
8595# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 15
8696def set_parents (node : Node , parent : Node | None = None ) -> Node :
@@ -95,6 +105,7 @@ def set_parents(node: Node, parent: Node|None=None) -> Node:
95105
96106# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 21
97107def create_n_nodes (num = 5 ) -> Node :
108+ """Creates a perfect binary tree with num nodes."""
98109 assert num > 0
99110 nodes = [Node (i ) for i in range (num )]
100111 for i in range (num ):
@@ -108,16 +119,9 @@ def create_n_nodes(num=5) -> Node:
108119 nodes [right_index ].parent = nodes [i ]
109120 return nodes [0 ] # root node
110121
111- visualize_tree (create_n_nodes (1 ))
112- visualize_tree (create_n_nodes (5 ))
113- visualize_tree (create_n_nodes (9 ))
114-
115122# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 24
116123def dfs_inorder (node : Node ) -> Generator [Node , None , None ]:
117- """Depth-first search.
118-
119- Visits left subtree, then node, then right subtree
120- """
124+ """Inorder DFS traversal: left, node, right."""
121125 if node .left :
122126 yield from dfs_inorder (node .left )
123127 yield node
@@ -128,10 +132,7 @@ def dfs_inorder(node: Node) -> Generator[Node, None, None]:
128132
129133# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 25
130134def dfs_preorder (node : Node ) -> Generator [Node , None , None ]:
131- """Depth-first search.
132-
133- Visits the node, then left subtree, then right subtree.
134- """
135+ """Preorder DFS traversal: node, left, right."""
135136 yield node
136137 if node .left :
137138 yield from dfs_preorder (node .left )
@@ -141,10 +142,7 @@ def dfs_preorder(node: Node) -> Generator[Node, None, None]:
141142
142143# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 26
143144def dfs_postorder (node : Node ) -> Generator [Node , None , None ]:
144- """Depth-first search.
145-
146- Visits the left subtree, then right subtree, then the node
147- """
145+ """Postorder DFS traversal: left, right, node."""
148146 if node .left :
149147 yield from dfs_postorder (node .left )
150148 if node .right :
@@ -154,7 +152,7 @@ def dfs_postorder(node: Node) -> Generator[Node, None, None]:
154152
155153# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 28
156154def bfs (node : Node ) -> Generator [Node , None , None ]:
157- """Breadth-first search."""
155+ """Breadth-first search (level order traversal) ."""
158156 queue = deque ([node ])
159157 while queue :
160158 node = queue .popleft ()
@@ -166,45 +164,40 @@ def bfs(node: Node) -> Generator[Node, None, None]:
166164list (bfs (root ))
167165
168166# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 30
169- def insert_node (root : Node , key ):
170- """"""
167+ def insert_node (root : Node , value ):
168+ """Level-order BFS insert. Inserts a new node with the given value. """
171169 queue = deque ([root ])
172170 while queue :
173171 node = queue .popleft ()
174172 if node .left :
175173 queue .append (node .left )
176174 else :
177- node .left = Node (key = key , left = None , right = None , parent = node )
175+ node .left = Node (value = value , left = None , right = None , parent = node )
178176 break
179-
180177 if node .right :
181178 queue .append (node .right )
182179 else :
183- node .right = Node (key = key , left = None , right = None , parent = node )
180+ node .right = Node (value = value , left = None , right = None , parent = node )
184181 break
185-
186182
187183# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 33
188- def search_tree (root : Node , key , traversal_func : Callable [[Node ], Generator [Node , None , None ]]) -> Node | None :
184+ def search_tree (root : Node , value , traversal_func : Callable [[Node ], Generator [Node , None , None ]]) -> Node | None :
185+ """Search for a node with the given value using the specified traversal function."""
189186 for node in traversal_func (root ):
190- if node .value == key :
187+ if node .value == value :
191188 return node
192189 else :
193190 return None
194191
195192# %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 36
196- def delete_node (root : Node , key = None ) -> Node | None :
197- """Uses BFS to delete the node with a key and swap it with the right-most node."""
198-
193+ def delete_node (root : Node , value = None ) -> Node | None :
194+ """Deletes the node with the given value by swapping with the right-most node."""
199195 # if root has no children
200- if root .left or root .right :
201- pass
202- else :
196+ if not (root .left or root .right ):
203197 del root
204198 return None
205-
206- # find the deepest root node and its parent
207- queue : deque [tuple [Node , Node | None ]] = deque ([(root , None )]) # root has no parent
199+ # find the deepest node and its parent
200+ queue : deque [tuple [Node , Node | None ]] = deque ([(root , None )])
208201 last_parent = None
209202 last_node = None
210203 while queue :
@@ -213,7 +206,6 @@ def delete_node(root: Node, key=None) -> Node|None:
213206 queue .append ((last_node .left , last_node ))
214207 if last_node .right :
215208 queue .append ((last_node .right , last_node ))
216-
217209 # delete the leaf node
218210 if last_parent :
219211 if last_parent .right :
@@ -222,26 +214,22 @@ def delete_node(root: Node, key=None) -> Node|None:
222214 last_parent .left = None
223215 else :
224216 raise NotImplementedError ("Parent of right-most node not found." )
225-
226- # find the node with value == key
227- if last_node and root .value == key :
217+ # find the node with value == value
218+ if last_node and root .value == value :
228219 last_node .left = root .left
229220 last_node .right = root .right
230221 del root
231222 return last_node
232- elif last_node :
233- pass
234- else :
223+ elif not last_node :
235224 raise NotImplementedError
236-
237225 target_queue : deque [Node ] = deque ([root ])
238226 while target_queue :
239227 node = target_queue .popleft ()
240228 if node .left :
241229 if node .left is last_node :
242230 node .left = None
243231 return root
244- elif node .left .value == key :
232+ elif node .left .value == value :
245233 node .left = last_node
246234 last_node .parent = node
247235 return root
@@ -250,9 +238,41 @@ def delete_node(root: Node, key=None) -> Node|None:
250238 if node .right is last_node :
251239 node .right = None
252240 return root
253- elif node .right .value == key :
241+ elif node .right .value == value :
254242 node .right = last_node
255243 last_node .parent = node
256244 return root
257245 target_queue .append (node .right )
258246 return root
247+
248+ # %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 41
249+ def bst_insert (root : Node , new_node : Node ) -> None :
250+ """Insert a new node into a BST. Raises an exception if value exists."""
251+ if new_node .value < root .value :
252+ if root .left :
253+ bst_insert (root .left , new_node )
254+ else :
255+ root .left = new_node
256+ new_node .parent = root
257+ return
258+ elif root .value < new_node .value :
259+ if root .right :
260+ bst_insert (root .right , new_node )
261+ else :
262+ root .right = new_node
263+ new_node .parent = root
264+ return
265+ else :
266+ raise Exception (f"Value { root .value } already exists in this tree." )
267+
268+ # %% ../nbs/Data Structures/Binary Trees/binary_trees.ipynb 45
269+ def bst_search (root : Node | None , key ) -> Node :
270+ """Search for a key in a BST. Raises an exception if not found."""
271+ if root .value == key :
272+ return root
273+ elif key < root .value and root .left :
274+ return bst_search (root .left , key )
275+ elif root .right :
276+ return bst_search (root .right , key )
277+ else :
278+ raise Exception (f"Key { key } not found" )
0 commit comments