5 Best Ways to Find the Rightmost Node in a Binary Tree using Python

πŸ’‘ Problem Formulation: Given a binary tree data structure, how can we find the node that appears most to the right at any level? A binary tree is made up of nodes where each node contains a left and right reference and a data element. The rightmost node is the last node in order of that level when viewed from left to right. For example, if our input binary tree is a representation of numbers 1-7 in a perfect binary tree format, the nodes in the order of rightmost at each level would be 1, 3, and 7.

Method 1: Level Order Traversal with Queue

Level order traversal is a method to traverse the tree level by level using a queue. We can track the last element of each level during traversal to find the rightmost node. A typical implementation uses a loop to process nodes while a queue keeps track of the nodes at the current and next level.

Here’s an example:

from collections import deque

class TreeNode:
    def __init__(self, value=0, left=None, right=None):
        self.val = value
        self.left = left
        self.right = right

def findRightmostNode(root):
    if not root:
        return None
    queue = deque([root])
    rightmost = None
    while queue:
        rightmost = queue[0]
        for _ in range(len(queue)):
            node = queue.popleft()
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
    return rightmost.val

# Example Binary Tree
root = TreeNode(1, TreeNode(2), TreeNode(3))
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)

print(findRightmostNode(root))

The output of this code snippet will be: 7.

This code snippet defines a simple binary tree with 7 nodes and uses level order traversal to determine the rightmost node at each level. The rightmost element from the last level processed (in this case 7) is returned as the output.

Method 2: Recursive Depth-First Search (DFS)

This method involves a depth-first search where we recursively visit each node’s right child before its left child. The last visited node at each level during the traversal is the rightmost node. We use a variable to keep track of the maximum depth visited and update the rightmost node accordingly.

Here’s an example:

class TreeNode:
    # TreeNode definition as in Method 1.

def findRightmostNode(root):
    def dfs(node, depth):
        if not node:
            return
        
        if depth >= len(rightmost_nodes):
            rightmost_nodes.append(node)
        dfs(node.right, depth+1)
        dfs(node.left, depth+1)
    
    rightmost_nodes = []
    dfs(root, 0)
    return [node.val for node in rightmost_nodes]

# Binary Tree as defined in Method 1.
print(findRightmostNode(root))

The output of this code snippet will be: [1, 3, 7].

Here, the recursive function dfs traverses the tree in a depth-first manner, starting with the right child. It updates the rightmost nodes at each depth level, ultimately providing the rightmost node’s value at each level.

Method 3: Iterative Depth-First Search with Stack

This method uses an iterative approach to perform depth-first search instead of recursion. Using a stack, we simulate the recursive calls to visit the child nodes, where the right child is always visited before the left. This ensures the last visited node in the stack is the rightmost node.

Here’s an example:

class TreeNode:
    # TreeNode definition as in previous methods.

def findRightmostNode(root):
    stack = [(root, 0)]
    rightmost_nodes = {}
    while stack:
        node, depth = stack.pop()
        if node:
            if depth not in rightmost_nodes:
                rightmost_nodes[depth] = node.val
            stack.append((node.left, depth + 1))
            stack.append((node.right, depth + 1))
    return list(rightmost_nodes.values())

# Binary Tree as defined in earlier examples.
print(findRightmostNode(root))

The output of this code snippet will be: [1, 3, 7].

This code uses a stack to store the nodes and their corresponding depth. It ensures that the rightmost node at each level is recorded. It then returns the values of these nodes, indicating the rightmost nodes in the binary tree.

Method 4: Modified Preorder Traversal

In this method, we modify the standard preorder traversal to visit the right child before the left at each step. This slight change guarantees that the last node we visit at each level of the tree is the rightmost node. To keep track of the levels, we pass the current level on each recursive call.

Here’s an example:

class TreeNode:
    # TreeNode definition as in previous methods.

def findRightmostNode(root):
    rightmost = {}

    def preorder(node, level):
        if node:
            # Overwrite the value at this level
            rightmost[level] = node.val
            # Visit right before left
            preorder(node.right, level + 1)
            preorder(node.left, level + 1)

    preorder(root, 0)
    return list(rightmost.values())

# Binary Tree as defined in earlier methods.
print(findRightmostNode(root))

The output of this code snippet will be: [1, 3, 7].

This snippet demonstrates how a modified preorder traversal algorithm can be used to record the rightmost node at each level. The dictionary rightmost is continuously updated with the last node encountered at each level.

Bonus One-Liner Method 5: Pythonic Depth-First Search

For those who prefer concise and Pythonic code, this one-liner approach employs list comprehension with depth-first search to find the rightmost node of each level in the binary tree.

Here’s an example:

class TreeNode:
    # TreeNode definition as in previous methods.

def findRightmostNode(root):
   return [level[-1].val for level in dfs([(root, 0)])] if root else []

def dfs(stack):
    result = []
    while stack:
        temp_stack, temp_res = [], []
        for node, depth in stack:
            if len(result) <= depth:
                result.append([])
            result[depth].append(node)
            if node.left:
                temp_stack.append((node.left, depth+1))
            if node.right:
                temp_stack.append((node.right, depth+1))
        stack = temp_stack
    return result
    
# Binary Tree as defined in earlier methods.
print(findRightmostNode(root))

The output of this code snippet will be: [1, 3, 7].

This Pythonic code block shows how a depth-first search with list comprehension provides a concise way to achieve the same result. It traverses the tree gathering nodes at each level and selecting only the rightmost node’s value.

Summary/Discussion

In summary, there are several strategies to find the rightmost node in a binary tree:

  • Method 1: Level Order Traversal with Queue. Strengths: Intuitive and straightforward implementation. Weaknesses: Requires additional space for the queue proportional to the width of the tree.
  • Method 2: Recursive Depth-First Search (DFS). Strengths: More space-efficient on balanced trees. Weaknesses: May exceed recursion stack for very deep trees.
  • Method 3: Iterative Depth-First Search with Stack. Strengths: Avoids recursion stack limitations. Weaknesses: More complex code structure compared to recursion.
  • Method 4: Modified Preorder Traversal. Strengths: Easy to understand and modify from typical preorder traversal. Weaknesses: Similar to DFS, but could overwrite values multiple times.
  • Method 5: Pythonic Depth-First Search. Strengths: Compact and elegant. Weaknesses: Maybe less readable for those not familiar with Pythonic conventions.