Must-Know Problem-Solving Knowledge | Tree | Advanced Implementation | LeetCode Notes
š äøęēļ¼ē¹é«äøę
In-Order Traversal by Parent Field
In the previous post introducing Tree you may have noticed that when implementing the Binary Tree Node we declared a TreeNode *parent pointer but never used it.
We can also perform Inorder Traversal via the Parent Field. To make good use of *parent for traversal, two important functions need to be mentioned:
InorderSuccessor(): in inorder order (LVR), find the next node of the traversalInorderPredecessor(): in inorder order (LVR), find the previous node of the traversal

The concept mentioned here will be related to the BST (Binary Search Tree) later
Before implementing these two functions to realize Inorder Traversal, there are a few preliminary tasks to complete:
- In
main(), link up the*parentpointers of the different nodes - In
class BinaryTree, define six member functions
1 | // Link the parent pointers |
1 | class BinaryTree{ |
Leftmost and Successor
After the preliminary work, we define the member functions one by one, starting with Leftmost()
1 | TreeNode * BinaryTree::Leftmost(TreeNode *current){ |
This is intuitive: it gets, based on currentNode, the leftmost child node (i.e. the first node to visit in that subtree). If the input is the root node, the output is the starting node of the whole Inorder Traversal.
Next is Successor, which finds currentNodeās next node. There are two cases here:
Case 1: if currentNodeās right child isnāt NULL, then currentNodeās next node in order is the leftmost node in the subtree rooted at āCurrent->rightchildā. It sounds convoluted; for example, in the figure below when nodeB is the currentNode, its right child isnāt null, so its Successor is the leftmost node in the subtree rooted at its right child, i.e. nodeG.
Case 2: if currentNode has no right child, then currentNodeās next node is āan ancestor found via the left-child roleā. For example, in the figure below nodeH has no right child, so its Successor must be its ancestor, but it has to be an ancestor node not yet visited. Since the order is inorder (LVR), it must be an ancestor found via the left-child role to be the next node. So nodeH finds nodeA as its Successor.

A special reminder: when implementing Case 2, a problem occurs on certain trees, e.g. a skewed tree with only a left subtree, where the whole tree has no right subtree. In that case you must return NULL, meaning the root has no successor.
1 | TreeNode * BinaryTree::InorderSuccessor(TreeNode *current){ |
With Leftmost and InorderSuccessor we can actually perform Inorder Traversal; the whole thing is combined in Inorder_by_parent
1 | void BinaryTree::Inorder_by_parent(TreeNode *root){ |
Now calling T.Inorder_by_parent(T.root) in main() outputs
1 | D B G E H A F I C |
Rightmost and Predecessor
Rightmost and Predecessor are conceptually similar to Leftmost and Successor, basically just with left and right swapped:
What Rightmost does is find the rightmost node in the subtree rooted at currentNode
1 | TreeNode * BinaryTree::Rightmost(TreeNode *current){ |
And Predecessor represents currentNodeās previous node, whose position, like Successor, has two possibilities:
Case 1: if currentNodeās leftchild isnāt null, then currentNodeās previous node is the rightmost node in the subtree rooted at currentNode->leftchild. For example, in the figure below nodeCās Predecessor is the rightmost node of the subtree rooted at nodeF (L), i.e. nodeI.
Case 2: if currentNode has no leftchild, then currentNodeās previous node is āan ancestor found via the right-child roleā. For example, in the figure below nodeF has no leftchild, so by inorder order (LVR) its previous node must be its ancestor, but an already-visited ancestor, so the ancestor must be found via the right-child role. So the ancestor nodeC found by nodeF via the left-child role is not its Predecessor, so it keeps going up and finds nodeA, which is its Predecessor.

A special reminder: like Successor, when implementing Case 2 a problem occurs on certain trees, e.g. a skewed tree with only a right subtree, where the whole tree has no left subtree. In that case you must return NULL, meaning the root has no predecessor.
1 | TreeNode * BinaryTree::InorderPredecessor(TreeNode *current){ |
With Rightmost and InorderPredecessor we can complete the reverse Inorder Traversal of a Binary Tree, realized via Inorder_Reverse
1 | void BinaryTree::Inorder_Reverse(TreeNode *root){ |
Now calling T.Inorder_Reverse(T.root) in main() gives the reverse Inorder output of the tree nodes
1 | C I F A H E G B D |
For the full code see my GitHub
Rebuilding the Binary Tree
The brute-force Binary Tree construction in the previous post actually has some access-safety issues, because the defined pointers are all public, so that main can access them. Next weāll make some changes, putting the pointers into the private block.
Binary Search Tree (BST)
What is a Binary Search Tree?
A Binary Search Tree is a kind of Binary Tree whose nodes satisfy an ordering property: all left-subtree nodes <= n < right-subtree nodes, which must hold for every node n, meaning this inequality must hold for all descendants of a node

The equality part in the definition above may vary; some definitions donāt allow duplicate values in the tree, or duplicate values go on the right, so still go back to the problemās definition.
1 | For any node R, all nodes of the subtree rooted at its left node (left subtree) must be less than R |
Something satisfying the above definition is also called a Normal Binary Search Tree, while BSTs with other properties, like red-black trees, will be discussed later.
Constructing a Binary Search Tree
1 |
|
Search

The figure above uses Jujutsu Kaisen manga characters as an example, with temporary power values assigned to them (set arbitrarily, donāt take it seriously lol)
Successful search
If I want to search for KEY(1250), the flow is like this:
- First, entering the BST,
*currentpoints to the root, then KEY(1250) is compared with Tojiās power (500); since 1250 > 500, we go into the right subtree - current moves to Kenjaku (550), and comparing: 1250 > 550, so current moves to Kenjakuās right subtree, i.e. Gojo (2500)
- Comparing now: 2500 > 1250, so current moves to Gojoās left subtree, i.e. Yuta (1250)
- Comparing again: 1250 = 1250, a match is found, confirming the search result is Yuta, breaking out of the while loop and returning current
Failed search
If I want to search for KEY(6), the flow is:
- First, entering the BST,
*currentpoints to the root, then KEY(6) is compared with Tojiās power (500); since 500 > 6, we go into the left subtree - current moves to Itadoriās Grandpa (13), and comparing: 13 > 6, so current moves to Itadoriās Grandpaās right subtree, i.e. Itadori (12)
- Comparing now: 12 > 6, so current needs to move to Itadoriās left subtree, but Itadoriās left subtree is NULL
- current being NULL breaks out of the loop and returns NULL, meaning the search failed
1 | TreeNode *BST::search(int KEY){ |
Insertion

Continuing from Search, if you want to insert data, you must first find the appropriate insert position, then connect the node to the tree.
To find the appropriate insert position, you need two pointers: one responsible for finding the position, one pointing to the newly inserted nodeās parent.
First, the pointer finding the position is called *current, and the pointer pointing to the parent is called *parentInsert; these two pointers move in sync each time.
- First
*currententers the root node, at which point*parentInsertis the rootās parent, i.e. NULL - The character we want to insert now is Rika, with power 520, so KEY(520) is compared with
*currentās Key(500); 520 > 500, so Rika should be in Tojiās right subtree - So move
*currenttoward Tojiās right child (Kenjaku), and update*parentInsertto Toji (500) - Now compare Rikaās Key(520) with Kenjaku (550); 520 < 550, so Rika should be in Kenjakuās left subtree
- So
*currentmoves toward Kenjakuās left child (NULL), and*parentInsertis updated to Kenjaku (550) - Since
*currentis now NULL, break out of the loop, because the appropriate insert position has been found, i.e. the child of the current*parentInsert
Now we just need to judge whether to insert at the current parentās left or right child position, so compare Rika (520) with the parent Kenjaku (550); the position to insert is Kenjakuās left child position, so add the node at that position, as shown in the figure.
1 | void BST::insertBST(int key, string element){ |
You can use insertBST to build a BST tree
1 | int main(){ |
The inorderPrint() above can be realized via the leftmost() and Successor mentioned in the first half of this post.
1 | TreeNode *BST::leftmost(TreeNode *current){ |
1 | TreeNode *BST::Successor(TreeNode *current){ |
1 | void BST::inorderPrint(){ |
After running the program, the output is as follows:
1 | Inorder Traversal: |
This looks like the output is correct
Sort
Take a look at the earlier figure; the tree above can actually be seen as the result of an Inorder Traversal on a tree. Because the BST definition L subTree <= n < R subTree matches the Inorder (L<V<R) order.
So running inorderPrint() is visiting the nodes in the BST in inorder order
Deletion
Since deleting a node affects all nodes connected to it (leftchild, rightchild, parent), the following classifies three handling situations:
- Case 1: the node to delete is a leaf, with no child pointer
- Case 2: the node to delete has only one child (whether leftchild or rightchild)
- Case 3: the node to delete has two children
Case 1
The solution is simple: just delete the leaf node directly, and point the original parentās child pointer at NULL

Case 2
Case 2, the node to delete has one child. In this case you must point its childās *parent at the to-be-deleted nodeās *parent, and the parent nodeās child pointer at that nodeās child, then perform the node deletion
For example, if we want to delete Gojo (2500), we must
- First point Yuta (1250)ās
*parentat Gojoās parent node Kenjaku (550) - Point Kenjaku (550)ās
rightchildat Yuta (1250); since Yuta was already in Kenjakuās right subtree, doing this still maintains the BST - Delete the node Gojo (2500)

Case 3
Case 3, the node to delete has two children, affecting more nodes, but thereās a good method:
Rather than deleting the node directly, free the memory address of its Successor or Predecessor, then replace the to-be-deleted nodeās data with the original Successorās or Predecessorās value.
For example, if I want to delete Choso (50), I can free the memory address of its Successor Todo (70) (or Predecessor Nanami (48)), then put the Successorās data value Todo (70) (or Predecessor Nanami (48)) back into Choso (50)ās memory address.

How to realize this can be simplified by a BST property: āa node with two children, its Successor or Predecessor must be a Leaf Node or a node with only one childā. Here we can give examples to verify this property:
Itadoriās Grandpa (13)ās Successor is Nanami (48), Predecessor is Itadori (12)
Toji (500)ās Successor is Kenjaku (550), Predecessor is Itadoriās Grandpa (13)
Successor: find the minimum value in the right subtree
Predecessor: find the maximum value in the left subtree
So the problem simplifies to Case 1 and Case 2
Implementation
Here we can define a new member function in the earlier BST class
1 | void DeleteBST(int key); |
The flow of implementing DeleteBST is as follows:
- First confirm via
Search()whether the node to delete exists in the BST - Adjust the pointer whose memory will actually be freed to a node with āat most one childā
- Point the actually-freed nodeās child at its new parent
- Point the actually-freed nodeās parent at its new child
- If the actually-freed memory is the āstand-inā, put the stand-inās data back into the BST
1 | void BST::DeleteBST(int KEY){ |
Then add T.DeleteBST(50); (delete Choso) in main(), and the output is as follows:
1 | Inorder Traversal: |
Delete Toji (500), the root:
1 | Inorder Traversal: |
Conclusion
This time I learned to hand-code a Binary Tree and implement many BST-related operations, referencing a lot of material and completing it step by step. There are surely better or simpler ways to write it; hopefully later I can combine it with the STL and actually use it while grinding LeetCode.








