diff --git a/algorithms/graphs/alien_dictionary/test_alien_dictionary.py b/algorithms/graphs/alien_dictionary/test_alien_dictionary.py index f7f8e7a2..3bc51a67 100644 --- a/algorithms/graphs/alien_dictionary/test_alien_dictionary.py +++ b/algorithms/graphs/alien_dictionary/test_alien_dictionary.py @@ -73,5 +73,6 @@ def is_valid_alien_order(self, words: List[str], order: str) -> bool: return False return True + if __name__ == "__main__": unittest.main() diff --git a/datastructures/trees/binary/README.md b/datastructures/trees/binary/README.md index c702c89a..90fc4a90 100644 --- a/datastructures/trees/binary/README.md +++ b/datastructures/trees/binary/README.md @@ -65,3 +65,80 @@ entire height of the tree, including `h` steps. The space complexity of this solution is `O(1)` as there is no additional space being used. Only two pointers are being maintained requiring constant space. +--- + +## Connect All Siblings of a Binary Tree + +Given the root of a perfect binary tree, where each node is equipped with an additional pointer, next, connect all nodes +from left to right. Do so in such a way that the next pointer of each node points to its immediate right sibling except +for the rightmost node, which points to the first node of the next level. + +The next pointer of the last node of the binary tree (i.e., the rightmost node of the last level) should be set to NULL. + +> A binary tree in which all the levels are completely filled with nodes, and all leaf nodes (nodes with no children) +> are at the same level. + +### Constraints + +- The number of nodes in the tree is in the range [0,500]. +- -1000 <= `node.data` <= 1000 + +### Examples + +![Example 1](./images/examples/connect_all_siblings_of_binary_tree_example_1.png) +![Example 2](./images/examples/connect_all_siblings_of_binary_tree_example_2.png) + +### Solution + +The algorithm connects all nodes by utilizing the structure of the perfect binary tree, where each non-leaf node has +exactly two children. Using this property, the algorithm connects nodes without needing extra space. It uses two pointers: +one to traverse the current level and another to connect nodes at the next level. Starting from the root, the algorithm +links each node’s left child to its right child and then connects the right child to the next node’s left child on the +same level, continuing this process across all nodes at that level. If no adjacent node is on the same level, the right +child’s next pointer is connected to the first node on the next lower level. This process continues until all nodes are +connected in a manner that reflects level-order traversal. + +The steps of the algorithm are given below: + +1. If the root is None, the tree is empty. In this case, return immediately. +2. Initialize two pointers current and last to the root node. The current pointer traverses the nodes level by level, + while the last keeps track of the last node connected via the next pointer. +3. The loop continues as long as current.left exists. In a perfect binary tree, all non-leaf nodes have a left child, + so this condition ensures that the loop continues until all levels are processed. + - First connection: last.next = current.left connects the last node (initially the root) to the current.left child. + After this, last is updated to current.left. + - Second connection: last.next = current.right connects current.left (now pointed to by last) to current.right. last + is then updated to current.right. + - Move to the next node: current = current.next moves the current pointer to the next node. +4. Finally, return the modified root of the tree, where all nodes are connected to their next sibling in the level-order + traversal. + +Let’s look at the following illustration(s) to get a better understanding of the solution: + +![Solution 1](./images/solutions/connect_all_siblings_of_binary_tree_solution_1.png) +![Solution 2](./images/solutions/connect_all_siblings_of_binary_tree_solution_2.png) +![Solution 3](./images/solutions/connect_all_siblings_of_binary_tree_solution_3.png) +![Solution 4](./images/solutions/connect_all_siblings_of_binary_tree_solution_4.png) +![Solution 5](./images/solutions/connect_all_siblings_of_binary_tree_solution_5.png) +![Solution 6](./images/solutions/connect_all_siblings_of_binary_tree_solution_6.png) +![Solution 7](./images/solutions/connect_all_siblings_of_binary_tree_solution_7.png) +![Solution 8](./images/solutions/connect_all_siblings_of_binary_tree_solution_8.png) +![Solution 9](./images/solutions/connect_all_siblings_of_binary_tree_solution_9.png) +![Solution 10](./images/solutions/connect_all_siblings_of_binary_tree_solution_10.png) +![Solution 11](./images/solutions/connect_all_siblings_of_binary_tree_solution_11.png) +![Solution 12](./images/solutions/connect_all_siblings_of_binary_tree_solution_12.png) +![Solution 13](./images/solutions/connect_all_siblings_of_binary_tree_solution_13.png) +![Solution 14](./images/solutions/connect_all_siblings_of_binary_tree_solution_14.png) +![Solution 15](./images/solutions/connect_all_siblings_of_binary_tree_solution_15.png) +![Solution 16](./images/solutions/connect_all_siblings_of_binary_tree_solution_16.png) +![Solution 17](./images/solutions/connect_all_siblings_of_binary_tree_solution_17.png) + +#### Time Complexity + +The time complexity of the solution is O(N), where N is the number of nodes in the binary tree. The algorithm traverses +each node exactly once, connecting the left and right children as it moves through the tree. + +#### Space Complexity + +The space complexity of the solution is O(1) because the algorithm uses only a constant amount of extra space, +specifically the pointers current and last, regardless of the size of the tree. diff --git a/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_1.png b/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_1.png new file mode 100644 index 00000000..f91ce166 Binary files /dev/null and b/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_1.png differ diff --git a/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_2.png b/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_2.png new file mode 100644 index 00000000..dcf3cacd Binary files /dev/null and b/datastructures/trees/binary/images/examples/connect_all_siblings_of_binary_tree_example_2.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_1.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_1.png new file mode 100644 index 00000000..eefd8ac9 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_1.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_10.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_10.png new file mode 100644 index 00000000..68b646d8 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_10.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_11.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_11.png new file mode 100644 index 00000000..52369d81 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_11.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_12.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_12.png new file mode 100644 index 00000000..2f7296f3 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_12.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_13.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_13.png new file mode 100644 index 00000000..96ff8bc9 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_13.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_14.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_14.png new file mode 100644 index 00000000..073d44e0 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_14.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_15.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_15.png new file mode 100644 index 00000000..f6a551a7 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_15.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_16.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_16.png new file mode 100644 index 00000000..13d379e7 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_16.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_17.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_17.png new file mode 100644 index 00000000..0b8d269e Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_17.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_2.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_2.png new file mode 100644 index 00000000..973d00ba Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_2.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_3.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_3.png new file mode 100644 index 00000000..efc5bd67 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_3.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_4.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_4.png new file mode 100644 index 00000000..ddd2a12d Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_4.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_5.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_5.png new file mode 100644 index 00000000..dea45c45 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_5.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_6.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_6.png new file mode 100644 index 00000000..355b679b Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_6.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_7.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_7.png new file mode 100644 index 00000000..27a09e07 Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_7.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_8.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_8.png new file mode 100644 index 00000000..f6435b5c Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_8.png differ diff --git a/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_9.png b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_9.png new file mode 100644 index 00000000..bbe816bf Binary files /dev/null and b/datastructures/trees/binary/images/solutions/connect_all_siblings_of_binary_tree_solution_9.png differ diff --git a/datastructures/trees/binary/node.py b/datastructures/trees/binary/node.py index 33b4efb2..9284055c 100644 --- a/datastructures/trees/binary/node.py +++ b/datastructures/trees/binary/node.py @@ -17,6 +17,7 @@ def __init__( right: Optional["BinaryTreeNode"] = None, key: Optional[Any] = None, parent: Optional["BinaryTreeNode"] = None, + next: Optional["BinaryTreeNode"] = None, ) -> None: """ Constructor for BinaryTreeNode class. This will create a new node with the provided data and optional @@ -29,10 +30,19 @@ def __init__( right (Optional[BinaryTreeNode]): Right child of the node key (Optional[Any]): Key for the node, if not provided a hash of the data is used parent (Optional[BinaryTreeNode]): Parent of the node + next (Optional[BinaryTreeNode]): Next child of the node which is the sibling of the node. The sibling is the + node on the same level as this node. If this is the rightmost node in the tree that is not on the last level + of the tree, then this is the next node on the next level starting from the left. If this is the last node + in the tree, then this is None. """ super().__init__(data, key, parent) self.left: Optional[BinaryTreeNode] = left self.right: Optional[BinaryTreeNode] = right + # Next is a pointer that connects this node to it's right sibling in the tree. If this node is the right most + # node on a given level, then it is connected to the first node on the next level. If this node is the last node + # in the tree on the last node, then it is pointed to None. By default, it is set to None. + # Note that if this is the root node, it is connected to the left most node on the next level. + self.next: Optional[BinaryTreeNode] = next def insert_node(self, data: T) -> None: """ @@ -181,7 +191,9 @@ def height(self) -> int: pass def __repr__(self): - return f"BinaryTreeNode(data={self.data}, key={self.key}, left={self.left}, right={self.right})" + parent_data = self.parent.data if self.parent else None + next_data = self.next.data if self.next else None + return f"BinaryTreeNode(data={self.data}, key={self.key}, left={self.left}, right={self.right}, parent={parent_data}, next={next_data})" def __eq__(self, other: "BinaryTreeNode") -> bool: """Checks if this node is equal to another node based on the data they contain @@ -193,7 +205,11 @@ def __eq__(self, other: "BinaryTreeNode") -> bool: if other is None: return False - if other.data == self.data: + if ( + other.data == self.data + and self.left == other.left + and self.right == other.right + ): return True return False diff --git a/datastructures/trees/binary/test_utils.py b/datastructures/trees/binary/test_utils.py index 1bdc47cc..fba86d26 100644 --- a/datastructures/trees/binary/test_utils.py +++ b/datastructures/trees/binary/test_utils.py @@ -1,7 +1,11 @@ import unittest +from typing import List +from parameterized import parameterized from datastructures.trees.binary.utils import ( lowest_common_ancestor, lowest_common_ancestor_ptr, + connect_all_siblings, + connect_all_siblings_ptr, ) from datastructures.trees.binary.node import BinaryTreeNode @@ -182,5 +186,41 @@ def test_4(self): self.assertEqual(expected, actual) +CONNECT_ALL_SIBLINGS_TEST_CASES = [ + ( + BinaryTreeNode( + data=100, + left=BinaryTreeNode( + data=50, left=BinaryTreeNode(data=25), right=BinaryTreeNode(data=75) + ), + right=BinaryTreeNode( + data=200, left=BinaryTreeNode(data=300), right=BinaryTreeNode(data=10) + ), + ), + [100, 50, 200, 25, 75, 300, 10], + ), +] + + +class ConnectAllSiblingsTestCase(unittest.TestCase): + @parameterized.expand(CONNECT_ALL_SIBLINGS_TEST_CASES) + def test_connect_all_siblings(self, root: BinaryTreeNode, expected: List[int]): + actual = connect_all_siblings(root) + current = actual + for expected_val in expected: + self.assertEqual(current.data, expected_val) + current = current.next + self.assertIsNone(current) + + @parameterized.expand(CONNECT_ALL_SIBLINGS_TEST_CASES) + def test_connect_all_siblings_ptr(self, root: BinaryTreeNode, expected: List[int]): + actual = connect_all_siblings_ptr(root) + current = actual + for expected_val in expected: + self.assertEqual(current.data, expected_val) + current = current.next + self.assertIsNone(current) + + if __name__ == "__main__": unittest.main() diff --git a/datastructures/trees/binary/tree/binary_tree.py b/datastructures/trees/binary/tree/binary_tree.py index 890c894e..3502a2ca 100644 --- a/datastructures/trees/binary/tree/binary_tree.py +++ b/datastructures/trees/binary/tree/binary_tree.py @@ -86,10 +86,10 @@ def level_order_traversal(self) -> List[Any]: return [] current_level: List[BinaryTreeNode] = [self.root] - levels: List[List[T]] = [] + levels: List[List[Any]] = [] while current_level: - level: List[T] = [] + level: List[Any] = [] next_level: List[BinaryTreeNode] = [] for node in current_level: diff --git a/datastructures/trees/binary/utils.py b/datastructures/trees/binary/utils.py index db2a8a82..0f151cf0 100644 --- a/datastructures/trees/binary/utils.py +++ b/datastructures/trees/binary/utils.py @@ -1,3 +1,5 @@ +from typing import Optional, Deque, List +from collections import deque from datastructures.trees.binary.node import BinaryTreeNode @@ -67,3 +69,96 @@ def lowest_common_ancestor_ptr( ptr2 = ptr2.parent if ptr2.parent else node_one return ptr1 + + +def connect_all_siblings(root: Optional[BinaryTreeNode]) -> Optional[BinaryTreeNode]: + """ + Connects all siblings of a binary tree given the root, such that, the right most node is connected to the first node + on the next level using a 'next' pointer forming a kind of linked list data structure. The right most node on the + last level is set to None. On each level the nodes are pointed to each other via the next pointer + This assumes that the provided root is part of a perfect binary tree. + + This uses a level order traversal utilizing a queue to traverse the nodes from the first level to the last level + adding the nodes to a list for further traversal. The levels list is then traversed connecting nodes to the next + node in the list via the next pointer. At this point the levels list will have the nodes in the correct order using + the level order traversal technique. + + After the traversal, the root node will be the first element in the levels list and we simply return that which + will contain the modified tree with all siblings connected + + This uses Space of O(n) as the levels list is required to handle the final iteration for the connections. + Time complexity is O(n) as we travers all the nodes in the tree. + + Args: + root(BinaryTreeNode): root of a perfect binary tree + Returns: + BinaryTreeNode root such that the next pointer has been set to point to siblings + """ + if not root: + return root + + # Queue will have the root node initially + queue: Deque[BinaryTreeNode] = deque([root]) + levels: List[BinaryTreeNode] = [root] + + while queue: + node = queue.popleft() + if node.left: + queue.append(node.left) + levels.append(node.left) + if node.right: + queue.append(node.right) + levels.append(node.right) + + for idx in range(len(levels) - 1): + levels[idx].next = levels[idx + 1] + + return levels[0] + + +def connect_all_siblings_ptr( + root: Optional[BinaryTreeNode], +) -> Optional[BinaryTreeNode]: + """ + Connects all siblings of a binary tree given the root, such that, the right most node is connected to the first node + on the next level using a 'next' pointer forming a kind of linked list data structure. The right most node on the + last level is set to None. On each level the nodes are pointed to each other via the next pointer + This assumes that the provided root is part of a perfect binary tree. + + This performs a level order traversal using two pointers to traverse the tree utilizing constant space in the process + making it efficient in terms of space. However, this incurs a time complexity of O(n) as all the nodes in the tree + have to be traversed from the root to make the connections + + Args: + root(BinaryTreeNode): root of a perfect binary tree + Returns: + BinaryTreeNode root such that the next pointer has been set to point to siblings + + """ + # If the tree is empty, there's nothing to connect + if root is None: + return root + + # Initialize two pointers: + # 'current' points to the current node being processed + # 'last' keeps track of the last node that was connected + current = root + last = root + + # Loop continues until there are no more left children in the current level + while current.left: + # Connect the last node's 'next' to the current node's left child + last.next = current.left + # Update 'last' to point to this left child + last = last.next + + # Connect the last node's 'next' to the current node's right child + last.next = current.right + # Update 'last' to point to this right child + last = last.next + + # Move 'current' to the next node at the same level + current = current.next + + # Return the root of the modified tree + return root