Skip to content

Commit 7cec5b1

Browse files
committed
feat(algorithms, data structures, binary search tree): inorder successor in bst
1 parent a8eded6 commit 7cec5b1

13 files changed

+157
-17
lines changed

datastructures/trees/binary/search_tree/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,65 @@ When the traversal hits the rightmost node, the stack will hold half of the n to
300300
so our worst case space cost is O(n).
301301

302302
Bonus What if the input tree has duplicate values?
303+
304+
---
305+
306+
## Inorder Successor in BST
307+
308+
You are given the root node of a binary search tree and a specific node p. Your task is to return the inorder successor
309+
of this p node. If there is no inorder successor of the given node, return NULL.
310+
311+
> Note: The inorder successor of p is the node with the smallest value greater than p.data in the binary search tree.
312+
313+
**Constraints**
314+
315+
- The tree contains nodes in the range [1, 500]
316+
- −10^4 ≤ `Node.data` ≤ 10^4
317+
- All Nodes will have unique values.
318+
- `p` should exist in the tree.
319+
320+
### Examples
321+
322+
![Example 1](./images/examples/inorder_successor_in_bst_example_1.png)
323+
![Example 2](./images/examples/inorder_successor_in_bst_example_2.png)
324+
![Example 3](./images/examples/inorder_successor_in_bst_example_3.png)
325+
326+
## Solution
327+
328+
To solve this problem, we will use the depth-first search pattern because it allows us to perform an inorder traversal
329+
of the tree, which is necessary to find the inorder successor of a given node.
330+
The properties of the BST allow us to make an efficient search by discarding half of the tree at each step. We start
331+
from the root node and compare the value of the current node with p. It helps us decide whether to move to the left or
332+
right subtree. If p is greater than or equal to the current node’s value, we move to the right subtree, as the in-order
333+
successor must be in the right subtree or above the current node. Otherwise, we explore the left subtree to find a
334+
potentially smaller successor. This way, we efficiently find the inorder successor of the given node.
335+
336+
Let’s go through the algorithm to see how we will reach the solution:
337+
338+
- Initialize a variable successor to NULL. It stores the potential inorder successor as we traverse the tree.
339+
- Traverse the tree starting from the root, and for each node, compare the values of p and root:
340+
- If the value of p is greater than or equal to the value of the root, the inorder successor must be in the right
341+
subtree or higher up in the tree. We move to the right subtree by setting root = root.right.
342+
- Otherwise, we update the successor to the current node, as this node is a potential in-order successor. Then, move
343+
to the left subtree by setting root = root.left.
344+
345+
- After the loop ends, we return the successor. This contains the inoder successor of the given node.
346+
347+
> Note: If there was no in-order successor of the given node, the successor will remain NULL.
348+
349+
![Solution 1](./images/solutions/inorder_successor_in_bst_solution_1.png)
350+
![Solution 2](./images/solutions/inorder_successor_in_bst_solution_2.png)
351+
![Solution 3](./images/solutions/inorder_successor_in_bst_solution_3.png)
352+
![Solution 4](./images/solutions/inorder_successor_in_bst_solution_4.png)
353+
![Solution 5](./images/solutions/inorder_successor_in_bst_solution_5.png)
354+
![Solution 6](./images/solutions/inorder_successor_in_bst_solution_6.png)
355+
![Solution 7](./images/solutions/inorder_successor_in_bst_solution_7.png)
356+
357+
### Time Complexity
358+
359+
The time complexity of this solution is O(n) in the worst-case scenario where the given tree is skewed. However, for a
360+
balanced binary search tree, it will be O(logn).
361+
362+
### Space Complexity
363+
364+
The space complexity of the solution is O(1) because we don’t use any additional space.

datastructures/trees/binary/search_tree/__init__.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,27 @@ def __init__(self, root: Optional[BinaryTreeNode] = None):
1212
super().__init__(root)
1313
self.stack = DynamicSizeStack()
1414

15+
def __len__(self) -> int:
16+
if not self.root:
17+
return 0
18+
19+
counter = 1
20+
stack = DynamicSizeStack()
21+
stack.push(self.root)
22+
23+
while not stack.is_empty():
24+
node = stack.pop()
25+
26+
if node.left:
27+
counter += 1
28+
stack.push(node.left)
29+
30+
if node.right:
31+
counter += 1
32+
stack.push(node.right)
33+
34+
return counter
35+
1536
@staticmethod
1637
def construct_bst(items: List[T]) -> Optional["BinarySearchTree"]:
1738
"""
@@ -42,7 +63,7 @@ def construct_bst_helper(left: int, right: int) -> Optional[BinaryTreeNode]:
4263

4364
return BinarySearchTree(root=construct_bst_helper(0, len(items) - 1))
4465

45-
def insert_node(self, data: T):
66+
def insert_node(self, data: Optional[T]):
4667
"""
4768
Inserts a node in a BST given an element
4869
If there is no root, then create a new root node with the data and return it
@@ -64,7 +85,8 @@ def insert_helper(value: T, node: BinaryTreeNode) -> BinaryTreeNode:
6485
node.right = insert_helper(value, node.right)
6586
return node
6687

67-
insert_helper(data, self.root)
88+
if data:
89+
insert_helper(data, self.root)
6890

6991
def delete_node(self, key: T) -> Optional[BinaryTreeNode]:
7092
"""Deletes a node from the Binary Search Tree. If the node is found, it is deleted and the tree re-ordered to
@@ -116,6 +138,7 @@ def delete_helper(
116138
else:
117139
node.right = lift(node.right, node)
118140
return node
141+
return None
119142

120143
def lift(
121144
node: BinaryTreeNode, node_to_delete: BinaryTreeNode
@@ -660,6 +683,7 @@ def search_helper(current: Optional[BinaryTreeNode], value: T) -> bool:
660683
return search_helper(current.right, value)
661684
else:
662685
return search_helper(current.left, value)
686+
return False
663687

664688
return search_helper(self.root, data)
665689

@@ -801,23 +825,43 @@ def paths(self) -> list:
801825

802826
return [list(map(int, x.split("->"))) for x in res]
803827

804-
def __len__(self) -> int:
805-
if not self.root:
806-
return 0
828+
def inorder_successor(self, node: BinaryTreeNode) -> Optional[BinaryTreeNode]:
829+
"""
830+
Returns the inorder successor of the node. If there is no node, None is returned. The inorder successor of the
831+
node is the node with the smallest value greater than node.data in the binary search tree.
807832
808-
counter = 1
809-
stack = DynamicSizeStack()
810-
stack.push(self.root)
833+
This assumes that the node is in the tree already.
811834
812-
while not stack.is_empty():
813-
node = stack.pop()
835+
Complexity:
814836
815-
if node.left:
816-
counter += 1
817-
stack.push(node.left)
837+
Time Complexity: The time complexity of this solution is O(n) in the worst-case scenario where the given tree
838+
is skewed. However, for a balanced binary search tree, it will be O(logn).
818839
819-
if node.right:
820-
counter += 1
821-
stack.push(node.right)
840+
Space Complexity: O(1) because we don't use any additional space.
822841
823-
return counter
842+
Args:
843+
node (BinaryTreeNode): node to search for inorder successor
844+
Returns:
845+
Optional[BinaryTreeNode]: returns inorder successor of node if available, else None
846+
"""
847+
if not self.root:
848+
return None
849+
850+
successor = None
851+
current = self.root
852+
853+
# current is the best candidate so far
854+
while current:
855+
# when current.data is greater than the node.data, we have found a valid successor, so, we save it in the
856+
# successor variable
857+
if current.data > node.data:
858+
successor = current
859+
# Move left to find a better(smaller) candidate. By moving to current.left, we are exploring values that
860+
# are smaller than current.data. Since we want the smallest possible successor, we must check the left
861+
# side of current to see if there is a node that is still greater than node.data, but close to node.data
862+
current = current.left
863+
else:
864+
# this node is too small, so we must go right to find a larger value
865+
current = current.right
866+
867+
return successor
39.1 KB
Loading
33 KB
Loading
30.1 KB
Loading
32.8 KB
Loading
29.4 KB
Loading
35.9 KB
Loading
31.5 KB
Loading
32 KB
Loading

0 commit comments

Comments
 (0)