Open In App

Recursion on Trees in Python

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 using Recursive Algorithms on Trees in Python:

Below is the implementation of the above code:

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)

Output
Depth-First Search (DFS) Recursive:
1
2
4
5
3
6
7

Auxiliary Space: O(n)
Time Complexity: O(n)

Tree Height and Depth using Recursion in Python:

Below is the implementation of the above code:

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))

Output
Height of the tree: 3
Depth of the tree: 3

Auxiliary Space: O(n)
Time Complexity: O(n)

Tree Size using Recursion in Python:

Below is the implementation of the above code:

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))

Output
Size of the tree: 7

Auxiliary Space: O(n)
Time Complexity: O(n)

Finding Maximum/Minimum Node in Tree using Recursion in Python

Below is the implementation of the above code:

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))

Output
Maximum element in the tree: 8
Minimum element in the tree: 2

Auxiliary Space: O(n)
Time Complexity: O(n)

Check for Symmetry in Tree using Recursion in Python:

Below is the implementation of the above code:

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))

Output
Is the symmetric tree symmetric? True
Is the non-symmetric tree symmetric? False

Auxiliary Space: O(n)
Time Complexity: O(n)

Path Finding in Tree using Recursion in Python:

Below is the implementation of the above code:

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.")

Output
Path to node 5: [1, 2, 5]

Auxiliary Space : O(n * h)
Time Complexity: O(n)

Finding Lowest Common Ancestor using Recursion in Python:

Below is the implementation of the above code:

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.")

Output
LCA 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:

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)

Output
Morris In-order Traversal:
4 2 5 1 3 

Auxiliary Space: O(1)
Time Complexity: O(n)

Article Tags :
DSA