Binary Search Tree (BST)
Written and reviewed by Gagan Bhardwaj · Senior IT Faculty · 15+ years’ experience
Binary Search Tree Model and Invariant
A binary search tree (BST) is a binary tree with an ordering invariant. Under the distinct-key policy used here, every key in a node's left subtree is smaller than the node key, and every key in its right subtree is greater. The rule applies recursively to every subtree.
50
/ \
30 70
/ \ / \
20 40 60 80The tree shape depends on insertion order. The same keys can form a short tree or a chain. Therefore “BST search is O(log n)” is not an unconditional guarantee.
Representing Nodes with unique_ptr
#include <memory>
#include <utility>
struct Node {
int key;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
explicit Node(int value) : key(value) {}
};Each node exclusively owns its children, so std::unique_ptr expresses the tree's ownership directly. When the root is destroyed, recursive member destruction releases all nodes. This avoids manual delete and the leak/double-delete risks of raw owning pointers. Learn the model in C++ smart pointers.
Non-owning observation may still use const Node* temporarily, but ownership remains with the unique pointers.
Insertion and Search
void insert(std::unique_ptr<Node>& root, int key) {
if (!root) {
root = std::make_unique<Node>(key);
} else if (key < root->key) {
insert(root->left, key);
} else if (key > root->key) {
insert(root->right, key);
}
// Equal keys are ignored by this explicit policy.
}
bool contains(const std::unique_ptr<Node>& root, int key) {
if (!root) return false;
if (key == root->key) return true;
return key < root->key ? contains(root->left, key)
: contains(root->right, key);
}Both operations follow one root-to-leaf path. Passing the owning pointer by reference lets insertion replace an empty child link. Search accepts a const reference because it does not change ownership or nodes.
Inorder, Preorder and Postorder Traversal
| Traversal | Visit order | Typical purpose |
|---|---|---|
| Inorder | Left, node, right | Ascending keys in a BST |
| Preorder | Node, left, right | Process root before subtrees |
| Postorder | Left, right, node | Process children before parent |
| Level order | Breadth by depth | Shape and shortest-edge levels |
void inorder(const std::unique_ptr<Node>& root) {
if (!root) return;
inorder(root->left);
std::cout << ' ' << root->key;
inorder(root->right);
}Each full traversal takes Θ(n) time because every node is visited once. Recursive auxiliary stack space is O(h).
The Three BST Deletion Cases
- No child: replace the owning link with null.
- One child: move that child into the target's owning link.
- Two children: copy the smallest key in the right subtree (inorder successor), then delete that key from the right subtree.
int minimum_key(const Node* node) {
while (node->left) node = node->left.get();
return node->key;
}
void erase(std::unique_ptr<Node>& root, int key) {
if (!root) return;
if (key < root->key) erase(root->left, key);
else if (key > root->key) erase(root->right, key);
else if (!root->left) root = std::move(root->right);
else if (!root->right) root = std::move(root->left);
else {
const int successor = minimum_key(root->right.get());
root->key = successor;
erase(root->right, successor);
}
}Moving a unique pointer transfers ownership safely. The successor has no left child, so the recursive deletion reduces to a simpler case.
Height, Complexity and Balancing
| Operation | Cost |
|---|---|
| Search | O(h) |
| Insert | O(h) |
| Delete | O(h) |
| Min/max | O(h) |
| Full traversal | Θ(n) |
| Storage | Θ(n) |
In a reasonably balanced tree, h=Θ(log n). Inserting already sorted keys into this unbalanced BST makes h=Θ(n), turning operations linear and risking deep recursion. AVL and red-black trees use rotations and balance metadata to control height. Standard std::set and std::map provide logarithmic search/insert/erase complexity requirements; use them instead of a teaching BST when their interface fits. See set and map.
Complete Modern C++ BST Lab
#include <iostream>
#include <memory>
#include <utility>
struct Node {
int key;
std::unique_ptr<Node> left, right;
explicit Node(int value) : key(value) {}
};
void insert(std::unique_ptr<Node>& root, int key) {
if (!root) root = std::make_unique<Node>(key);
else if (key < root->key) insert(root->left, key);
else if (key > root->key) insert(root->right, key);
}
bool contains(const std::unique_ptr<Node>& root, int key) {
if (!root) return false;
if (key == root->key) return true;
return key < root->key ? contains(root->left, key)
: contains(root->right, key);
}
void inorder(const std::unique_ptr<Node>& root) {
if (!root) return;
inorder(root->left);
std::cout << ' ' << root->key;
inorder(root->right);
}
int minimum_key(const Node* node) {
while (node->left) node = node->left.get();
return node->key;
}
void erase(std::unique_ptr<Node>& root, int key) {
if (!root) return;
if (key < root->key) erase(root->left, key);
else if (key > root->key) erase(root->right, key);
else if (!root->left) root = std::move(root->right);
else if (!root->right) root = std::move(root->left);
else {
int successor = minimum_key(root->right.get());
root->key = successor;
erase(root->right, successor);
}
}
int main() {
std::unique_ptr<Node> root;
for (int key : {50, 30, 70, 20, 40, 60, 80}) insert(root, key);
std::cout << "Inorder:"; inorder(root); std::cout << '\n';
std::cout << "Contains 60: " << std::boolalpha
<< contains(root, 60) << '\n';
erase(root, 30);
std::cout << "After deleting 30:"; inorder(root); std::cout << '\n';
}Deleting 30 exercises the two-child case: 40 is its inorder successor.
Common Mistakes, Tests and Practice
| Mistake | Correction |
|---|---|
| Applying ordering only to direct children | Maintain it for entire subtrees |
| No duplicate policy | Document reject/count/side rule |
| Losing a child during delete | Transfer ownership with move |
| Calling every BST balanced | Analyze actual height |
| Raw owning pointers without cleanup | Use RAII ownership |
| Dereferencing null root | Check base case first |
Test empty-tree search/delete, root deletion, leaf, one child, two children, absent key, duplicate insertion and skewed input. Practice iterative search, height calculation, validation with lower/upper bounds, predecessor/successor and level-order traversal.
Authoritative References
The definitions and ordering property were checked against these references. Continue with smart pointers and ordered containers.
Frequently Asked Questions
How is a BST different from a binary tree?
What should a BST do with duplicate keys?
Why does inorder traversal produce sorted keys?
What is the time complexity of BST operations?
What are the three BST deletion cases?
BST Model और Invariant
Binary search tree में हर node की left subtree की सभी keys छोटी और right subtree की सभी keys बड़ी होती हैं। Rule हर subtree पर recursively लागू होता है। इस program में duplicate key ignore होती है।
50
/ \
30 70
/ \ / \
20 40 60 80Insertion order tree shape बदलता है; हर BST balanced नहीं होता।
Node Ownership
struct Node {
int key;
std::unique_ptr<Node> left, right;
explicit Node(int value) : key(value) {}
};Node अपने children को exclusively own करता है। Root destroy होने पर पूरा tree automatic release होता है; manual delete की जरूरत नहीं। Smart pointers पढ़ें।
Insert और Search
Key छोटी हो तो left, बड़ी हो तो right path लें। Null link पर new node बनता है। Search भी एक root-to-leaf path follow करता है और null मिलने पर false देता है। Owning pointer reference insert को empty child replace करने देता है।
Tree Traversals
| Traversal | Order | Use |
|---|---|---|
| Inorder | Left, node, right | BST की ascending keys |
| Preorder | Node, left, right | Root first |
| Postorder | Left, right, node | Children first |
| Level order | Depth-wise | Tree shape |
Full traversal Θ(n) time और recursive O(h) stack लेता है।
Deletion के तीन Cases
- Leaf: owning link null करें।
- One child: child ownership target link में move करें।
- Two children: right subtree की minimum key (successor) copy कर उसके original node को delete करें।
unique_ptr move ownership सुरक्षित transfer करता है।
Height और Complexity
| Operation | Cost |
|---|---|
| Search/Insert/Delete | O(h) |
| Balanced height | Θ(log n) |
| Skewed height | Θ(n) |
| Traversal | Θ(n) |
| Storage | Θ(n) |
Sorted insertion chain बना सकता है। Height guarantee के लिए AVL/red-black concept और practical ordered data के लिए std::set/std::map देखें।
Complete Modern C++ Lab
std::unique_ptr<Node> root;
for (int key : {50,30,70,20,40,60,80}) insert(root,key);
std::cout << "Inorder:"; inorder(root);
std::cout << std::boolalpha << contains(root,60);
erase(root,30);English section का full compile-ready program insert, search, inorder और three-case deletion implement करता है। 30 के successor 40 से replacement होता है।
Mistakes, Tests और Practice
- Ordering केवल direct child पर नहीं, whole subtree पर लागू है।
- Duplicate policy document करें।
- Delete में child ownership न खोएं।
- Actual height analyze करें; BST को automatic balanced न कहें।
- Null base case पहले रखें।
Empty, leaf, one-child, two-child, root, absent और skewed cases test करें। Height और BST validator practice करें।
Authoritative संदर्भ
Definitions और ordering invariant authoritative sources से verify हैं।