Recursion on Trees in Python
Last Updated :
16 Apr, 2024
In Python, recursion is implemented by defining a function that makes a call to itself within its definition. This process continues until a base case is reached, which is a condition where the function returns a value without making any further recursive calls. Without a base case, the recursion would continue indefinitely, leading to what's known as "infinite recursion," which can cause the program to crash.
- Depth-First Search (DFS) is a traversal algorithm that explores as far as possible along each branch before backtracking.
- In a recursive implementation, we typically perform a depth-first traversal by recursively visiting the child nodes of each node.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def dfs_recursive(node):
if node is None:
return
# Visit the current node
print(node.value)
# Traverse left subtree
dfs_recursive(node.left)
# Traverse right subtree
dfs_recursive(node.right)
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Perform DFS recursively
print("Depth-First Search (DFS) Recursive:")
dfs_recursive(root)
OutputDepth-First Search (DFS) Recursive:
1
2
4
5
3
6
7
Auxiliary Space: O(n)
Time Complexity: O(n)
- The height of a tree is the length of the longest path from the root node to a leaf node.
- The depth of a node is the length of the path from the root to that node.
- These can be calculated recursively by traversing the tree and keeping track of the depth or height as we go.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def tree_height(node):
if node is None:
return 0
else:
left_height = tree_height(node.left)
right_height = tree_height(node.right)
return max(left_height, right_height) + 1
def tree_depth(node):
if node is None:
return 0
else:
left_depth = tree_depth(node.left)
right_depth = tree_depth(node.right)
return max(left_depth, right_depth) + 1
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Calculate height and depth
print("Height of the tree:", tree_height(root))
print("Depth of the tree:", tree_depth(root))
OutputHeight of the tree: 3
Depth of the tree: 3
Auxiliary Space: O(n)
Time Complexity: O(n)
Tree Size using Recursion in Python:
- The size of a tree is the total number of nodes in the tree, including the root node and all its descendants.
- This can be calculated recursively by summing up the sizes of the left and right subtrees and adding 1 for the root node.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def tree_size(node):
if node is None:
return 0
else:
left_size = tree_size(node.left)
right_size = tree_size(node.right)
return left_size + right_size + 1
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Calculate the size of the tree
print("Size of the tree:", tree_size(root))
OutputSize of the tree: 7
Auxiliary Space: O(n)
Time Complexity: O(n)
- To find the maximum or minimum element in a tree, we can recursively traverse the tree and compare values at each node.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def find_max(node):
if node is None:
return float('-inf')
else:
max_left = find_max(node.left)
max_right = find_max(node.right)
return max(node.value, max_left, max_right)
def find_min(node):
if node is None:
return float('inf')
else:
min_left = find_min(node.left)
min_right = find_min(node.right)
return min(node.value, min_left, min_right)
# Example usage:
# Create a binary tree
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(7)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.left = TreeNode(6)
root.right.right = TreeNode(8)
# Find maximum and minimum elements
print("Maximum element in the tree:", find_max(root))
print("Minimum element in the tree:", find_min(root))
OutputMaximum element in the tree: 8
Minimum element in the tree: 2
Auxiliary Space: O(n)
Time Complexity: O(n)
- Checking for symmetry in a tree involves comparing the left and right subtrees of the root recursively.
- At each step, we compare corresponding nodes in the left and right subtrees.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def is_symmetric(left, right):
# Base case: If both nodes are None, they are symmetric
if left is None and right is None:
return True
# If only one node is None or their values are different, they are not symmetric
if left is None or right is None or left.value != right.value:
return False
# Recursively check the symmetry of corresponding nodes in the left and right subtrees
return is_symmetric(left.left, right.right) and is_symmetric(left.right, right.left)
def check_symmetry(root):
# Empty tree is symmetric
if root is None:
return True
# Check the symmetry of the left and right subtrees
return is_symmetric(root.left, root.right)
# Example usage:
# Create a symmetric binary tree
symmetric_root = TreeNode(1)
symmetric_root.left = TreeNode(2)
symmetric_root.right = TreeNode(2)
symmetric_root.left.left = TreeNode(3)
symmetric_root.left.right = TreeNode(4)
symmetric_root.right.left = TreeNode(4)
symmetric_root.right.right = TreeNode(3)
# Create a non-symmetric binary tree
non_symmetric_root = TreeNode(1)
non_symmetric_root.left = TreeNode(2)
non_symmetric_root.right = TreeNode(2)
non_symmetric_root.left.right = TreeNode(3)
non_symmetric_root.right.right = TreeNode(3)
# Check symmetry
print("Is the symmetric tree symmetric?", check_symmetry(symmetric_root))
print("Is the non-symmetric tree symmetric?", check_symmetry(non_symmetric_root))
OutputIs the symmetric tree symmetric? True
Is the non-symmetric tree symmetric? False
Auxiliary Space: O(n)
Time Complexity: O(n)
- Path finding involves finding a path from the root of the tree to a given node.
- We can use recursion to traverse the tree and keep track of the path.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def find_path(root, target, path=None):
if path is None:
path = []
if root is None:
return []
path.append(root.value)
if root.value == target:
return path
left_path = find_path(root.left, target, path.copy())
right_path = find_path(root.right, target, path.copy())
if left_path:
return left_path
elif right_path:
return right_path
else:
return []
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Find path to node 5
target_node = 5
path_to_node = find_path(root, target_node)
if path_to_node:
print(f"Path to node {target_node}: {path_to_node}")
else:
print(f"Node {target_node} not found in the tree.")
OutputPath to node 5: [1, 2, 5]
Auxiliary Space : O(n * h)
Time Complexity: O(n)
- The Lowest Common Ancestor (LCA) of two nodes in a binary tree is the lowest node in the tree that has both nodes as descendants.
- We can use recursion to find the LCA of two given nodes.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def find_lca(root, node1, node2):
if root is None:
return None
# If the root is one of the nodes, it is the LCA
if root.value == node1 or root.value == node2:
return root
# Recursively find LCA in left and right subtrees
left_lca = find_lca(root.left, node1, node2)
right_lca = find_lca(root.right, node1, node2)
# If both nodes are found in different subtrees, root is the LCA
if left_lca and right_lca:
return root
# Otherwise, return the non-empty subtree
return left_lca if left_lca else right_lca
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Find LCA of nodes 4 and 5
node1_value = 4
node2_value = 5
lca_node = find_lca(root, node1_value, node2_value)
if lca_node:
print(f"LCA of nodes {node1_value} and {node2_value} is: {lca_node.value}")
else:
print(f"Nodes {node1_value} and {node2_value} are not found in the tree or they don't have a common ancestor.")
OutputLCA of nodes 4 and 5 is: 2
Auxiliary Space: O(n)
Time Complexity: O(n)
Morris Traversal using Recursion in Python:
Morris Traversal is an in-order tree traversal algorithm that does not use recursion or a stack. Instead, it modifies the tree structure by linking the rightmost node of the left subtree to the current node, allowing traversal without additional space.
Below is the implementation of the above code:
Python3
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def morris_inorder_traversal(root):
current = root
while current:
if current.left is None:
print(current.value, end=" ")
current = current.right
else:
# Find the rightmost node in the left subtree
predecessor = current.left
while predecessor.right and predecessor.right != current:
predecessor = predecessor.right
if predecessor.right is None:
predecessor.right = current
current = current.left
else:
predecessor.right = None
print(current.value, end=" ")
current = current.right
# Example usage:
# Create a binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
# Perform Morris in-order traversal
print("Morris In-order Traversal:")
morris_inorder_traversal(root)
OutputMorris In-order Traversal:
4 2 5 1 3
Auxiliary Space: O(1)
Time Complexity: O(n)
Similar Reads
DSA Tutorial - Learn Data Structures and Algorithms
DSA (Data Structures and Algorithms) is the study of organizing data efficiently using data structures like arrays, stacks, and trees, paired with step-by-step procedures (or algorithms) to solve problems effectively. Data structures manage how data is stored and accessed, while algorithms focus on
7 min read
Non-linear Components
In electrical circuits, Non-linear Components are electronic devices that need an external power source to operate actively. Non-Linear Components are those that are changed with respect to the voltage and current. Elements that do not follow ohm's law are called Non-linear Components. Non-linear Co
11 min read
Quick Sort
QuickSort is a sorting algorithm based on the Divide and Conquer that picks an element as a pivot and partitions the given array around the picked pivot by placing the pivot in its correct position in the sorted array. It works on the principle of divide and conquer, breaking down the problem into s
13 min read
Merge Sort - Data Structure and Algorithms Tutorials
Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows the divide-and-conquer approach. It works by recursively dividing the input array into two halves, recursively sorting the two halves and finally merging them back together to obtain the sorted array. How do
14 min read
Breadth First Search or BFS for a Graph
Given a undirected graph represented by an adjacency list adj, where each adj[i] represents the list of vertices connected to vertex i. Perform a Breadth First Search (BFS) traversal starting from vertex 0, visiting vertices from left to right according to the adjacency list, and return a list conta
15+ min read
Bubble Sort Algorithm
Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in the wrong order. This algorithm is not suitable for large data sets as its average and worst-case time complexity are quite high. We sort the array using multiple passes. After the fi
8 min read
Binary Search Algorithm - Iterative and Recursive Implementation
Binary Search Algorithm is a searching algorithm used in a sorted array by repeatedly dividing the search interval in half. The idea of binary search is to use the information that the array is sorted and reduce the time complexity to O(log N). Conditions to apply Binary Search Algorithm in a Data S
15+ min read
Insertion Sort Algorithm
Insertion sort is a simple sorting algorithm that works by iteratively inserting each element of an unsorted list into its correct position in a sorted portion of the list. It is like sorting playing cards in your hands. You split the cards into two groups: the sorted cards and the unsorted cards. T
9 min read
Data Structures Tutorial
Data structures are the fundamental building blocks of computer programming. They define how data is organized, stored, and manipulated within a program. Understanding data structures is very important for developing efficient and effective algorithms. What is Data Structure?A data structure is a st
2 min read
Selection Sort
Selection Sort is a comparison-based sorting algorithm. It sorts an array by repeatedly selecting the smallest (or largest) element from the unsorted portion and swapping it with the first unsorted element. This process continues until the entire array is sorted. First we find the smallest element a
8 min read