5 Best Ways to Find Sibling Value of a Binary Tree Node in Python

Rate this post

πŸ’‘ Problem Formulation: In binary trees, two nodes are considered siblings if they are children of the same parent node. This article aims to discuss different methods to find the value of the sibling node given a binary tree and a reference node in Python. For instance, if the input binary tree is depicted as 1 -> (2, 3), where 1 is the root node and 2, 3 are its children, and the input reference node is 2, the desired output would be the value of the sibling node, which is 3.

Method 1: Recursive Tree Traversal

This method leverages a depth-first search through the binary tree using recursion, to find the sibling of a node. The parent is checked at each step whether it contains the reference node; if it does, the sibling is returned. The function signature could be such as find_sibling(root, val), where root is the root of the binary tree and val is the value of the reference node.

Here’s an example:

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

def find_sibling(root, val):
    if not root or root.val == val:
        return None
    if root.left and root.left.val == val:
        return root.right.val if root.right else None
    if root.right and root.right.val == val:
        return root.left.val if root.left else None
    return find_sibling(root.left, val) or find_sibling(root.right, val)

# Example binary tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)

# Find the sibling of node 2
print(find_sibling(root, 2))

Output: 3

This code snippet first defines a TreeNode class to represent each node in a binary tree, followed by a find_sibling function that takes the root of the binary tree and the value of the reference node as arguments. The function traverses the tree recursively, looking for the node that has the reference value and then returns the value of the sibling node if it exists.

Method 2: Level Order Traversal using Queue

By performing a level order traversal using a queue, we can track sibling nodes in a binary tree. We traverse each level, insert the children into a queue, and upon finding the target node, we identify its adjacent node in the queue as its sibling. The function signature might be something like find_sibling_by_level(root, val).

Here’s an example:

from collections import deque

def find_sibling_by_level(root, val):
    if not root:
        return None
    queue = deque([root])
    while queue:
        level_size = len(queue)
        for i in range(level_size):
            node = queue.popleft()
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
            if node.val == val:
                return queue[0].val if i < level_size - 1 else None
    return None

# Use the TreeNode class and tree from Method 1

# Find the sibling of node 2
print(find_sibling_by_level(root, 2))

Output: 3

This snippet makes use of the Breadth-First Search pattern through a queue to traverse each level of the binary tree. For each node, it checks if the node’s value matches the reference value and uses the queue’s ordering to find the sibling node, if any.

Method 3: Iterative Depth-First Search

In the iterative Depth-First Search approach, we use a stack to simulate the recursive process. Each node, along with its parent, is stored in the stack while traversing the tree. If the target node is found, the sibling can be retrieved using the parent node. Implement a function find_sibling_iterative(root, val) that iterates over the tree to find the sibling.

Here’s an example:

def find_sibling_iterative(root, val):
    stack = [(root, None)]
    while stack:
        node, parent = stack.pop()
        if node:
            if node.val == val:
                if parent and parent.left == node and parent.right:
                    return parent.right.val
                if parent and parent.right == node and parent.left:
                    return parent.left.val
                return None
            stack.append((node.right, node))
            stack.append((node.left, node))
    return None

# Use the TreeNode class and tree from Method 1

# Find the sibling of node 2
print(find_sibling_iterative(root, 2))

Output: 3

The code uses a stack to conduct a depth-first traversal. As is the nature of DFS, it goes deep into nodes one path at a time. It cleverly pairs each node with its parent in a tuple so that upon finding the target node, the sibling can be easily identified.

Method 4: Hash Map of Parent Nodes

By creating a hash map that maps each node to its parent node, we can find sibling nodes post-traversal. This method requires two passes: one to populate the hash map, and one to identify the sibling node using this map. The function definition could look like find_sibling_with_parent_map(root, val).

Here’s an example:

def find_sibling_with_parent_map(root, val):
    parent_map = {}
    stack = [root]
    while stack:
        node = stack.pop()
        if node.right:
            parent_map[node.right.val] = node
            stack.append(node.right)
        if node.left:
            parent_map[node.left.val] = node
            stack.append(node.left)
    target_parent = parent_map.get(val, None)
    if target_parent:
        if target_parent.left and target_parent.left.val == val:
            return target_parent.right.val if target_parent.right else None
        if target_parent.right and target_parent.right.val == val:
            return target_parent.left.val if target_parent.left else None
    return None

# Use the TreeNode class and tree from Method 1

# Find the sibling of node 2
print(find_sibling_with_parent_map(root, 2))

Output: 3

The snippet demonstrates a method where a dictionary, parent_map, contains the parent-child relationships. After populating this map through an iterative traversal, the map is used to retrieve the parent of the reference node, and subsequently, its sibling. This method is especially useful in scenarios where multiple sibling lookups might be required, as the map only needs to be built once.

Bonus One-Liner Method 5: Using Recursive Comprehension

A one-liner Python method can be written to find the sibling by comprehending the binary tree recursively. It’s succinct but requires a solid understanding of recursive structures and list comprehension. This approach is compact and quite Pythonic, employing a function find_sibling_oneliner(root, val) to perform the task in a single line.

Here’s an example:

def find_sibling_oneliner(root, val):
    return [sibling.val for sibling in (root.left, root.right) if sibling and sibling.val != val] or None

# Use the TreeNode class and tree from Method 1

# Find the sibling of node 2
print(find_sibling_oneliner(root, 2) or "No sibling found")

Output: [3]

This compact snippet uses a list comprehension that iterates through the children of the root node and returns the value of the sibling if it’s different from the reference value. It elegantly wraps the entire logic into a single line of code; however, it works only when invoked directly on the parent of the reference node.

Summary/Discussion

  • Method 1: Recursive Tree Traversal. This method is straightforward and embodies the classic recursive pattern found in binary tree problems. However, it might not be the most efficient for wider trees due to the call stack size.
  • Method 2: Level Order Traversal using Queue. Well-suited to find siblings since it inherently processes nodes in sibling pairs. The queue ensures we respect the tree’s levels but uses extra space.
  • Method 3: Iterative Depth-First Search. Converts the recursive approach into an iterative one, which can handle wider trees better, but it still requires tracking parents, which adds complexity.
  • Method 4: Hash Map of Parent Nodes. Efficient for multiple sibling queries. It builds a reusable parent-child map but requires additional space proportionate to the number of nodes in the tree.
  • Bonus One-Liner Method 5: Using Recursive Comprehension. Pythonic and concise, but not as flexible, since it only works when called on the parent node and doesn’t scale to deeper binary tree structures.