Binary Search Trees
A binary search tree is a binary tree that maintains the binary-search-tree property for every node in the tree. The binary-search-tree property states that given a node \(x\) in the tree, every node in the left subtree has a key less than \(x\)’s key and every node in the right subtree has a key greater than \(x\)’s key.
Motivation
But why invent another data structure? Let’s take a look at sorted arrays. We can search a sorted array for keys in just \(O(\log(n))\) time with binary search. However, inserting and deleting elements takes \(O(n)\) time. Similarly, while inserting elements in a linked list takes only \(O(1)\) time, searching a linked list takes \(O(n)\) time in the worst case. Can we do better with binary search trees? yes!
The binary search tree property is really great at allowing us to insert/search and delete in just \(O(h)\) time since we can eliminate a branch at every single step. Moreover, if the tree is balanced, the height will only be \(O(\log(n))\) where is \(n\) is the number of nodes. Overall, this is a much better data structure for dynamic data than both arrays and linked lists!
In Order Walk
Another great property of binary search trees is that an in order walk of the tree results in getting all the keys sorted.
Proving that it takes \(O(n)\) time to perform the in order walk is such a great way to practice the substitution method. (TODO: add proof)
Search
Similar to the in-order walk, we can simply perform a search by using the following
Minimum, Maximum, Predecessor and Successor
Similarly, we can find the minimum and maximum by traversing all the way to the left and all the way to the right respectively. For example to find the successor of a node \(x\), we have two cases:
- If \(x\) has a right subtree, then the most left element (tree minimum) of the right subtree is the successor.
- If \(x\) doesn’t have a right subtree, then the next element would be the first ancestor such that \(x\) is a left child of it.
Insert
Inserting a node into a binary search tree is pretty simple. We need to follow the following steps:
- Create a new node and assign both the left and right pointers to NULL.
- Similar to search, descend in the tree with pointer \(current\) based on the key value, while keeping a trailing pointer \(p\) to its parent. Once we hit NULL, we know that the \(p\) will be the parent of our node. The figure below illustrates the process:
Delete
Before discussing delete, we’ll present a helper function that we will use in deleting a node in a binary search three. transplant replaces a subtree rooted at \(x\) with another subtree rooted at \(v\), illustrated below,
Now, suppose we’re about to delete node \(x\) and that are given a pointer to it We have four different cases that we need to handle:
-
\(x\) has no children. We then can simply delete that node and return. This case could be handled implicitly in the next case.
-
\(x\) has only one child. We then just transplant its child at \(x\)’s parent and remove \(x\).
- \(x\) has two children. The idea here is that the successor of \(x\) will take \(x\)’s place to maintain the binary search tree property and then we can just delete \(x\). We do know that the successor is the most left child (minimum node) in \(x\)’s right subtree. Let \(s\) be the successor of \(x\). It is also important to note that \(s\) can’t have left children since it is the most left node by definition. We now break deleting \(x\) into two sub-cases:
(1) \(s\) is the right child of \(x\). In this case, we transplant \(s\) at \(x\)’s parent. We also move \(x\)’s left subtree to be \(s\)’s left subtree. We then we remove \(x\).
(2) \(s\) is in the left subtree of \(x\)’s right child. In this case, it’s a little more complicated. We first want to replace \(s\) (21 in the example) with its right subtree (25 in the example) (remember \(s\) can’t have a left subtree). We then assign \(x\)’s right subtree to be \(s\)’s right subtree. So now the subtree 29 is the right child of 21. Finally just like in the earlier case, simply replace \(x\) with its right subtree. In this example, it means to replace 19 with 21!
Finally putting everything together in one place:
Implementation
References
CLRS