All binary search trees have the property that each node is greater than or equal to all nodes in the left subtree, and less than or equal to all nodes in the right subtree.
When we want to search, we want to retrieve some piece of information by key from a large value of information. Recall from arrays and linked lists that there is worst-case \(O(n)\) retrieval information.
In BSTs, insert
is as easy to implement as search
.
Node in a binary tree:
template <typename KEY>
struct Node {
KEY key;
Node *left, *right;
}
Here are some traversal implementations:
void inorder(Node *x) {
if (!x) return;
inorder(x->left);
print(x->key);
inorder(x->right);
}
void preorder(Node *x) {
if (!x) return;
preorder(x->left);
print(x->key);
preorder(x->right);
}
void postorder(Node *x) {
if (!x) return;
postorder(x->left);
print(x->key);
postorder(x->right);
}
An inorder traversal is a way to get the values in order, since they are already stored in order.
What's the complexity of this?
This gives us a recurrence relation of \(\Theta (n)\)!
Node *tree_search(Node *x, Key k) {
while (x != nullptr && k != x->key) {
x = (k < x->key) ? x->left : x->right;
}
return x;
}
Complexity of this operation is \(O(h) = O(\log n)\) for the average case. For the worst case, it is \(O(n)\) if the tree is very stick-like (all elements except for one have same value).
This is relatively similar to search, but what happens with duplicates? You have to use (<=, >) or (<, >=).
void tree_insert(Node *&x, Key k) {
if (x == nullptr) {
x = new Node;
x->key = k;
x->left = x->right = nullptr;
}
else if (k < x->key)
tree_insert(x->left, k);
else
tree_insert(x->right, k);
}
The complexity of this is again, \(O(h) = O(\log n)\) for the average case. For the worst case, it is \(O(n)\).
If the node has two children, then replace the node with a "combined" tree of both. Well, all the nodes in the LHS subtree are <= all in the RHS subtree. All you do is transplant the smallest RHS node to root. Make this root's left child the LHS subtree!