-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTrees.py
More file actions
1689 lines (1076 loc) · 41.7 KB
/
Copy pathTrees.py
File metadata and controls
1689 lines (1076 loc) · 41.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## Tips:
#
#
#
#
# If you wanna understand Trees effectively please first complete Queue, Stack and Linked List. Mastering these topics will make learning Trees a breeze
# Good luck with learning Trees
## Imp watch this YT playlist to understand Binary Tree
## https://www.youtube.com/watch?v=6oL-0TdVy28&list=PL5tcWHG-UPH2fmYC6kgey1RIxP2iK9EEL&pp=iAQB
## Also you can find a nutshell code of whole topics and other binary methods in a single one ( check out last code )
# 15. Trees
# 15.1 Tree Data Structure
# A tree is a hierarchical data structure that consists of nodes, with a single node called the root. Each node can have zero or more child nodes, and each node has exactly one parent node, except for the root node, which has no parent. Trees are widely used in computer science for various purposes.
# Terminology:
# Root: The top node in a tree.
# Node: Each element in a tree.
# Edge: The connection between two nodes.
# Parent: The node above another node.
# Child: The node below another node.
# Leaf: A node with no children.
# Subtree: A tree consisting of a node and its descendants.
# Height: The length of the longest path from the root to a leaf.
# Depth: The length of the path from the root to a node.
# Tree Example:
# A
# / \
# B C
# / \ \
# D E F
## 15.2 Application of Tree
# Applications:
# Hierarchical Data: Representing hierarchical structures like file systems, organizational structures, etc.
# Searching Algorithms: Implementing search trees like Binary Search Trees (BST), AVL trees, etc.
# Routing Algorithms: Used in networking to determine the most efficient routing paths.
# Database Indexing: B-trees and their variants are used in database indexing.
# Expression Parsing: Abstract syntax trees in compilers for parsing expressions.
## 15.3 Binary Tree in Python
# A binary tree is a tree data structure where each node has at most two children, referred to as the left child and the right child.
# Binary Tree Implementation in Python:
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
# # Example usage:
# # Creating a simple binary tree
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
## 15.4 Tree Traversal
# Tree traversal refers to the process of visiting all the nodes of a tree in a specific order. There are three common types of tree traversal:
# Inorder Traversal (Left, Root, Right)
# Inorder traversal visits the nodes of a binary tree in the following order: left subtree, root node, right subtree.
# Inorder Traversal Implementation:
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.val, end=" ")
inorder_traversal(root.right)
# # Example usage:
inorder_traversal(root) # Output: 4 2 5 1 3
# Preorder Traversal (Root, Left, Right)
# Preorder traversal visits the nodes of a binary tree in the following order: root node, left subtree, right subtree.
# Preorder Traversal Implementation:
def preorder_traversal(root):
if root:
print(root.val, end=" ")
preorder_traversal(root.left)
preorder_traversal(root.right)
# # Example usage:
preorder_traversal(root) # Output: 1 2 4 5 3
# Postorder Traversal (Left, Right, Root)
# Postorder traversal visits the nodes of a binary tree in the following order: left subtree, right subtree, root node.
# Postorder Traversal Implementation:
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.val, end=" ")
# # Example usage:
postorder_traversal(root) # Output: 4 5 2 3 1
# Detailed Breakdown
# Node Class:
# class Node:
# def __init__(self, key):
# self.left = None
# self.right = None
# self.val = key
# self.left: Points to the left child of the node.
# self.right: Points to the right child of the node.
# self.val: Stores the value of the node.
# Creating a Binary Tree:
# root = Node(1)
# root.left = Node(2)
# root.right = Node(3)
# root.left.left = Node(4)
# root.left.right = Node(5)
# root = Node(1): Creates the root node with value 1.
# root.left = Node(2): Creates the left child of the root node with value 2.
# root.right = Node(3): Creates the right child of the root node with value 3.
# root.left.left = Node(4): Creates the left child of the node with value 2.
# root.left.right = Node(5): Creates the right child of the node with value 2.
# Inorder Traversal:
# def inorder_traversal(root):
# if root:
# inorder_traversal(root.left)
# print(root.val, end=" ")
# inorder_traversal(root.right)
# if root:: Checks if the current node is not None.
# inorder_traversal(root.left): Recursively visits the left subtree.
# print(root.val, end=" "): Prints the value of the current node.
# inorder_traversal(root.right): Recursively visits the right subtree.
# Preorder Traversal:
# def preorder_traversal(root):
# if root:
# print(root.val, end=" ")
# preorder_traversal(root.left)
# preorder_traversal(root.right)
# print(root.val, end=" "): Prints the value of the current node.
# preorder_traversal(root.left): Recursively visits the left subtree.
# preorder_traversal(root.right): Recursively visits the right subtree.
# Postorder Traversal:
# def postorder_traversal(root):
# if root:
# postorder_traversal(root.left)
# postorder_traversal(root.right)
# print(root.val, end=" ")
# postorder_traversal(root.left): Recursively visits the left subtree.
# postorder_traversal(root.right): Recursively visits the right subtree.
# print(root.val, end=" "): Prints the value of the current node.
# Traversal of a binary tree is a fundamental operation that allows us to visit each node in a specific order. The three common traversal methods—Inorder, Preorder, and Postorder—are used for various purposes depending on the specific requirements of an application or problem. Here’s a breakdown of each traversal method, its implementation, and its use cases:
# 1. Inorder Traversal (Left, Root, Right)
# Code:
# def inorder(self, node):
# if node:
# self.inorder(node.left)
# print(node.data, end=' -> ')
# self.inorder(node.right)
# Use Case:
# Binary Search Trees (BSTs): Inorder traversal of a BST retrieves the nodes in non-decreasing order. This is particularly useful for sorting and checking the properties of BSTs.
# Syntax Trees: It helps in converting the syntax tree into infix expressions, making it useful in compiler design.
# 2. Preorder Traversal (Root, Left, Right)
# Code:
# def preorder(self, node):
# if node:
# print(node.data, end=' -> ')
# self.preorder(node.left)
# self.preorder(node.right)
# Use Case:
# Tree Copying: Preorder traversal is used to create a copy of the tree.
# Prefix Expressions: It helps in converting the syntax tree into prefix expressions, useful in certain types of computations and evaluations.
# File Systems: Preorder traversal is used to list directories and files (like in a file explorer).
# 3. Postorder Traversal (Left, Right, Root)
# Code:
# def postorder(self, node):
# if node:
# self.postorder(node.left)
# self.postorder(node.right)
# print(node.data, end=' -> ')
# Use Case:
# Tree Deletion: Postorder traversal is used to delete a tree. This ensures that the children nodes are deleted before their parent node.
# Expression Evaluation: It helps in converting the syntax tree into postfix expressions, useful for evaluating expressions using a stack.
# Why Specific Orders?
# The traversal orders are not arbitrary; they are designed to serve specific purposes:
# Inorder Traversal: This visits nodes in non-decreasing order for BSTs, making it ideal for operations that require sorted data.
# Preorder Traversal: This is useful for creating a replica of the tree or producing prefix expressions (polish notation).
# Postorder Traversal: This is useful for deleting a tree or evaluating postfix expressions (reverse polish notation).
# Why Not Other Orders?
# The given traversal orders (Inorder, Preorder, Postorder) are the most commonly used because they address the most frequent needs in computer science and data structures. Traversing the right subtree before the left subtree is less common because the natural hierarchical representation of trees and binary search properties often make left-first traversals more useful. However, there are no strict rules against defining custom traversal orders if a particular application requires it.
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def inorder(self, root_node):
if root_node:
self.inorder(root_node.left)
print(root_node.data, end=' -> ')
self.inorder(root_node.right)
def preorder(self, root_node):
if root_node:
print(root_node.data, end=' -> ')
self.preorder(root_node.left)
self.preorder(root_node.right)
def postorder(self, root_node):
if root_node:
self.postorder(root_node.left)
self.postorder(root_node.right)
print(root_node.data, end=' -> ')
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
bi.inorder(bi.root)
print('')
bi.preorder(bi.root)
print('')
bi.postorder(bi.root)
## Level order traversal
# Level order traversal is a method of traversing a binary tree level by level, starting from the root. This traversal visits all nodes at the current level before moving on to nodes at the next level. It is also known as breadth-first traversal.
# Why Use a Queue?
# A queue is ideal for level order traversal because it operates on a first-in, first-out (FIFO) basis. This ensures that nodes are processed in the order they are encountered, maintaining the correct sequence for level-by-level traversal.
# Code Example and Explanation
# Here’s how you can implement level order traversal using a queue:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data) # Add data to the end of the queue
def get(self):
return self.queue.pop(0) # Remove and return data from the front of the queue
def is_empty(self):
return len(self.queue) == 0 # Check if the queue is empty
def peek(self):
if not self.is_empty():
return self.queue[0] # Return the front data without removing it
else:
return None
def get_size(self):
return len(self.queue) # Return the current size of the queue
class BinaryTree:
def __init__(self, data):
self.root = Node(data) # Initialize the binary tree with the root node
def level_order(self, root_node):
if not root_node:
return None # If the root node is None, return None
queue = Queue()
queue.put(root_node)
level_order_list = []
while not queue.is_empty():
out_node = queue.get() # Dequeue a node
level_order_list.append(out_node) # Append the node to the result list
if out_node.left:
queue.put(out_node.left) # Enqueue the left child
if out_node.right:
queue.put(out_node.right) # Enqueue the right child
return [node.data for node in level_order_list] # Extract data values from nodes
# Example usage:
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
print(bi.level_order(bi.root)) # Output: [1, 2, 3, 4, 5]
## reverse level traversal
# Reverse level order traversal is the process of traversing a binary tree level by level but starting from the bottom-most level and moving upwards to the root. This is essentially the opposite of the standard level order traversal.
# Approach
# To achieve reverse level order traversal, you can use the following approach:
# Use a Queue for Standard Level Order Traversal:
# Traverse the tree level by level, and use a queue to keep track of nodes.
# Use a Stack to Reverse the Order:
# Push each node's value onto a stack as you dequeue it from the queue. The stack will help reverse the order because it follows Last-In-First-Out (LIFO) principle.
# Output from the Stack:
# Pop nodes from the stack to get the reverse level order traversal.
# Code Example
# Here's how you can implement reverse level order traversal in Python:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Stack:
def __init__(self):
self.stack = []
def append(self, data):
self.stack.append(data)
def pop(self):
return self.stack.pop()
def is_empty(self):
return len(self.stack) == 0
def peek(self):
return self.stack[-1]
def get_size(self):
return len(self.stack)
def get_stack(self):
return self.stack
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)
def get(self):
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
def peek(self):
if not self.is_empty():
return self.queue[0]
else:
return None
def get_size(self):
return len(self.queue)
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def reverse_order_trav(self, root_node):
if root_node is None:
return []
stack = Stack()
queue = Queue()
queue.put(root_node)
while not queue.is_empty():
out_node = queue.get()
# Enqueue the right child first to ensure correct order in the stack
if out_node.right:
queue.put(out_node.right)
if out_node.left:
queue.put(out_node.left)
# Push the node onto the stack
stack.append(out_node)
reverse_level = []
# Pop nodes from the stack to get them in reverse level order
while not stack.is_empty():
reverse_level.append(stack.pop().data)
return reverse_level
# Example usage:
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
print(bi.reverse_order_trav(bi.root)) # Output: [4, 5, 3, 2, 1]
# When it comes to reverse level order traversal, the best method depends on factors like code simplicity, memory usage, and execution time. Here’s a comparison of common approaches:
# 1. Queue and Stack Method
# Description: Use a queue for level-order traversal and a stack to reverse the order.
# Pros:
# Straightforward and intuitive.
# Clear separation of concerns: queue handles level-order traversal, and stack handles reversal.
# Cons:
# Requires two additional data structures: a queue and a stack.
# Slightly more complex due to managing multiple structures.
# Code Example:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Stack:
def __init__(self):
self.stack = []
def append(self, data):
self.stack.append(data)
def pop(self):
return self.stack.pop()
def is_empty(self):
return len(self.stack) == 0
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)
def get(self):
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def reverse_level_order_trav(self, root_node):
if not root_node:
return []
queue = Queue()
stack = Stack()
queue.put(root_node)
while not queue.is_empty():
node = queue.get()
stack.append(node)
if node.right:
queue.put(node.right)
if node.left:
queue.put(node.left)
result = []
while not stack.is_empty():
result.append(stack.pop().data)
return result
# Time Complexity: O(n)
# Space Complexity: O(n)
# 2. Two Queues Method
# Description: Use one queue for level-order traversal and a second queue to reverse the order.
# Pros:
# Avoids the explicit use of a stack.
# Simple approach with clear logic.
# Cons:
# Requires an extra queue, which adds to the space complexity.
# Code Example:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)
def get(self):
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def reverse_level_order_trav(self, root_node):
if not root_node:
return []
level_queue = Queue()
reverse_queue = Queue()
level_queue.put(root_node)
while not level_queue.is_empty():
node = level_queue.get()
reverse_queue.put(node.data)
if node.right:
level_queue.put(node.right)
if node.left:
level_queue.put(node.left)
result = []
while not reverse_queue.is_empty():
result.append(reverse_queue.get())
return result
# Time Complexity: O(n)
# Space Complexity: O(n)
# 3. Single Queue with List Reversal Method
# Description: Perform level-order traversal using a single queue and reverse the collected nodes' values at the end.
# Pros:
# Simpler and uses only one additional data structure.
# Easy to understand and implement.
# Cons:
# Requires reversing the list of collected values, which adds an extra step.
# Code Example:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)
def get(self):
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def reverse_level_order_trav(self, root_node):
if not root_node:
return []
queue = Queue()
queue.put(root_node)
level_order_list = []
while not queue.is_empty():
node = queue.get()
level_order_list.append(node.data)
if node.left:
queue.put(node.left)
if node.right:
queue.put(node.right)
return level_order_list[::-1] # Reverse the list
# Example usage:
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
print(bi.reverse_level_order_trav(bi.root)) # Output: [4, 5, 3, 2, 1]
# Time Complexity: O(n)
# Space Complexity: O(n)
# Summary
# Queue and Stack Method: Offers clear separation of concerns but uses two additional data structures.
# Two Queues Method: Straightforward and avoids a stack but uses an extra queue.
# Single Queue with List Reversal Method: Simplest and efficient with minimal data structures, but involves an extra reversal step.
## Calculating Height of Tree
# Calculating the height of a binary tree is a common problem in tree data structure algorithms. The height of a binary tree is defined as the number of edges on the longest path from the root node to a leaf node. It can also be defined as the number of nodes along the longest path from the root node to the deepest leaf node, minus one.
# How to Calculate Tree Height
# 1. Definition
# Height of a Node: The height of a node is the number of edges on the longest path from the node to a leaf node.
# Height of a Tree: The height of the root node of a tree.
# Approach
# Recursive Approach
# The recursive approach is straightforward and intuitive. You compute the height of the left and right subtrees of a node and then use the larger of the two heights to compute the height of the node.
# Steps:
# Base Case: If the node is None, return -1 (for height definition) or 0 (for height definition including the node itself).
# Recursive Case: Compute the height of the left subtree and the height of the right subtree.
# Combine Results: The height of the current node is 1 + max(height_of_left_subtree, height_of_right_subtree).
# Code Example
# Here's how you can calculate the height of a binary tree using a recursive approach in Python:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def height(self, node):
if node is None:
return -1 # For height definition (0-based) or return 0 for height definition including node itself
left_height = self.height(node.left)
right_height = self.height(node.right)
return 1 + max(left_height, right_height)
# Example usage
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
bi.root.right.right = Node(7)
print("Height of the tree:", bi.height(bi.root)) # Output will be 2 (0-based height)
# Explanation:
# if node is None:: This is the base case. When you reach a leaf node's child (which is None), the height is -1 (or 0 if you include the node itself in the height).
# height(node.left) and height(node.right): Recursively calculate the height of the left and right subtrees.
# 1 + max(left_height, right_height): The height of the current node is 1 plus the maximum height of its two subtrees.
# Alternative Iterative Approach
# While the recursive approach is the most common, you can also compute the height of a binary tree iteratively using a level-order traversal (BFS) approach. Here's how you might do that:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)
def get(self):
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def height(self):
if not self.root:
return -1
queue = Queue()
queue.put(self.root)
height = -1
while not queue.is_empty():
level_size = len(queue.queue)
height += 1
for _ in range(level_size):
node = queue.get()
if node.left:
queue.put(node.left)
if node.right:
queue.put(node.right)
return height
# Example usage
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
bi.root.right.right = Node(7)
print("Height of the tree:", bi.height()) # Output will be 2
# Explanation:
# Level-order Traversal: Traverse the tree level by level.
# height Calculation: Increase the height count at the end of each level.
# Summary
# Recursive Approach: Simple and intuitive; uses the call stack to manage function calls.
# Iterative Approach: Uses a queue for level-order traversal; avoids recursion and can be more space-efficient for very deep trees.
## Size of a binary tree
# Calculating the size of a binary tree, which is the total number of nodes in the tree, can be done in various ways. Below are explanations and implementations for calculating the size of a binary tree using both recursive and iterative methods.
# Recursive Approach
# In the recursive approach, you traverse the tree and count nodes by summing the sizes of the left and right subtrees and adding one for the current node.
# Implementation
# Here's how you can implement the size calculation recursively:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self, data):
self.root = Node(data)
def size_of_tree(self, node):
""" Calculate the size of the tree recursively. """
if node is None:
return 0
else:
left_size = self.size_of_tree(node.left)
right_size = self.size_of_tree(node.right)
return left_size + right_size + 1
bi = BinaryTree(1)
bi.root.left = Node(2)
bi.root.right = Node(3)
bi.root.left.left = Node(4)
bi.root.left.right = Node(5)
print(bi.size_of_tree(bi.root)) # Output: 5
# Explanation
# Base Case:
# If the node is None, return 0 because there are no nodes.
# Recursive Case:
# Calculate the size of the left subtree.
# Calculate the size of the right subtree.
# Add 1 for the current node and return the total.
# Iterative Approach
# The iterative approach can be implemented using a breadth-first traversal (level-order traversal) or a depth-first traversal. Here’s how you can do it using a queue.
# Implementation
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class Queue:
def __init__(self):
self.queue = []
def put(self, data):
self.queue.append(data)