Binary Search Trees (BST)
Binary Search Tree (BST)
1. The BST Invariant (BST-এর মূল নিয়ম)
A binary tree is a Binary Search Tree if for every node:
- All values in the left subtree are strictly less than this node's value, AND
- All values in the right subtree are strictly greater than this node's value, AND
- Both left and right subtrees are themselves BSTs.
2. Why a BST?
A sorted array supports binary search in O(log n) but inserting a new element costs O(n)
because everything to the right must shift. A BST aims to give us both search and insert in
O(log n) — provided the tree stays roughly balanced. Real-world examples include the in-memory
indexes of database engines and the std::map / std::set containers in C++.
std::map এবং Java-র TreeMap এই idea-র production-grade balanced BST।
3. The Hidden Cost — When BSTs Degenerate
What happens if we insert 1, 2, 3, 4, 5 in that order into an empty BST? Each new value is greater
than the previous root, so it always goes to the right child. The tree degenerates into a linked list — depth
becomes n, and every operation costs O(n).
4. Insert & Search — The Easy Half
Both insert and search start at the root and descend. At every step, compare with the current node — if the target is smaller, go left; if larger, go right; if equal, stop (search) or ignore / handle duplicates as you wish (insert).
5. A Full BST — Insert, Search, Delete (3 cases)
Delete is the tricky operation. There are three cases:
- Leaf: simply free it.
- One child: link parent directly to that child.
- Two children: replace the value with the in-order successor (smallest in the right subtree), then delete that successor recursively.
#include <bits/stdc++.h>
using namespace std;
struct Node {
int v;
Node *l, *r;
Node(int x) : v(x), l(nullptr), r(nullptr) {}
};
Node* insert(Node* root, int x) {
if (!root) return new Node(x);
if (x < root->v) root->l = insert(root->l, x);
else if (x > root->v) root->r = insert(root->r, x);
return root; // duplicates ignored
}
bool search(Node* root, int x) {
while (root) {
if (x == root->v) return true;
root = (x < root->v) ? root->l : root->r;
}
return false;
}
Node* minNode(Node* r) { while (r->l) r = r->l; return r; }
Node* erase(Node* root, int x) {
if (!root) return nullptr;
if (x < root->v) root->l = erase(root->l, x);
else if (x > root->v) root->r = erase(root->r, x);
else {
if (!root->l) { Node* t = root->r; delete root; return t; }
if (!root->r) { Node* t = root->l; delete root; return t; }
Node* succ = minNode(root->r);
root->v = succ->v;
root->r = erase(root->r, succ->v);
}
return root;
}
void inorder(Node* r) {
if (!r) return;
inorder(r->l);
cout << r->v << ' ';
inorder(r->r);
}
int main() {
Node* root = nullptr;
for (int x : {50, 30, 70, 20, 40, 60, 80})
root = insert(root, x);
cout << "Inorder : "; inorder(root); cout << '\n';
cout << "Search 40 : " << search(root, 40) << '\n';
cout << "Search 100 : " << search(root, 100) << '\n';
root = erase(root, 30); // node with two children
cout << "After del30: "; inorder(root); cout << '\n';
return 0;
}
6. k-th Smallest in a BST (in-order trick)
Because in-order is sorted, the k-th element of in-order is the k-th smallest. We don't have to materialise the entire list — we can stop as soon as the counter reaches k.
#include <bits/stdc++.h>
using namespace std;
struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}};
N* ins(N* r,int x){
if(!r) return new N(x);
if(x<r->v) r->l=ins(r->l,x); else if(x>r->v) r->r=ins(r->r,x);
return r;
}
int cnt = 0, ans = -1;
void kth(N* r, int k){
if(!r || ans!=-1) return;
kth(r->l,k);
if(++cnt==k){ ans=r->v; return; }
kth(r->r,k);
}
int main(){
N* root=nullptr;
for(int x:{50,30,70,20,40,60,80}) root=ins(root,x);
kth(root,3);
cout << "3rd smallest = " << ans;
}
7. Complexity Cheat-Sheet
| Operation | Average | Worst (degenerate) |
|---|---|---|
| Search | O(log n) | O(n) |
| Insert | O(log n) | O(n) |
| Delete | O(log n) | O(n) |
| In-order traversal | O(n) | O(n) |
std::set ব্যবহার করুন — সেগুলো সবসময় O(log n) গ্যারান্টি দেয়।
8. Practice Problems
-
Validate whether a given binary tree is a BST (use min-max bound technique).একটি tree BST কিনা পরীক্ষা করুন (min-max bound পদ্ধতি)।
✨ Show Answer
validate.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; bool valid(N* r, long lo, long hi){ if(!r) return true; if(r->v <= lo || r->v >= hi) return false; return valid(r->l, lo, r->v) && valid(r->r, r->v, hi); } int main(){ N* r=new N(5); r->l=new N(3); r->r=new N(8); r->l->l=new N(1); r->l->r=new N(4); cout << (valid(r, LONG_MIN, LONG_MAX) ? "BST" : "NOT BST"); } -
Find the Lowest Common Ancestor (LCA) of two values in a BST.BST-তে দুটি মানের LCA বের করুন।
✨ Show Answer
lca.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N* ins(N* r,int x){if(!r)return new N(x); if(x<r->v) r->l=ins(r->l,x); else if(x>r->v) r->r=ins(r->r,x); return r;} N* lca(N* r,int a,int b){ while(r){ if(a<r->v && b<r->v) r=r->l; else if(a>r->v && b>r->v) r=r->r; else return r; } return nullptr; } int main(){ N* root=nullptr; for(int x:{50,30,70,20,40,60,80}) root=ins(root,x); cout << "LCA(20,40)=" << lca(root,20,40)->v << '\n'; cout << "LCA(20,80)=" << lca(root,20,80)->v; } -
Find
floor(x)andceil(x)in a BST (largest ≤ x and smallest ≥ x).BST-তে x-এর floor ও ceil বের করুন।✨ Show Answer
floor_ceil.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N* ins(N* r,int x){if(!r)return new N(x); if(x<r->v) r->l=ins(r->l,x); else if(x>r->v) r->r=ins(r->r,x); return r;} int floorBST(N* r,int x){ int ans=INT_MIN; while(r){ if(r->v==x) return x; if(r->v<x){ans=r->v; r=r->r;} else r=r->l; } return ans; } int ceilBST(N* r,int x){ int ans=INT_MAX; while(r){ if(r->v==x) return x; if(r->v>x){ans=r->v; r=r->l;} else r=r->r; } return ans; } int main(){ N* root=nullptr; for(int x:{10,20,30,40,50}) root=ins(root,x); cout << "floor(25)=" << floorBST(root,25) << " ceil(25)=" << ceilBST(root,25); } -
Range sum in a BST: sum all values x with L ≤ x ≤ R.BST-তে [L, R] range-এর সব মানের যোগফল।
✨ Show Answer
range_sum.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N* ins(N* r,int x){if(!r)return new N(x); if(x<r->v) r->l=ins(r->l,x); else if(x>r->v) r->r=ins(r->r,x); return r;} int rs(N* r,int L,int R){ if(!r) return 0; if(r->v<L) return rs(r->r,L,R); if(r->v>R) return rs(r->l,L,R); return r->v + rs(r->l,L,R) + rs(r->r,L,R); } int main(){ N* root=nullptr; for(int x:{10,5,15,3,7,18}) root=ins(root,x); cout << "sum[7..15] = " << rs(root,7,15); } -
Convert a sorted array into a balanced BST.Sorted array থেকে balanced BST তৈরি করুন।
✨ Show Answer
sorted_to_bst.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N* build(vector<int>& a, int lo, int hi){ if(lo>hi) return nullptr; int m=(lo+hi)/2; N* n=new N(a[m]); n->l=build(a,lo,m-1); n->r=build(a,m+1,hi); return n; } void in(N* r){if(!r)return; in(r->l); cout<<r->v<<' '; in(r->r);} int h(N* r){return r? 1+max(h(r->l),h(r->r)):0;} int main(){ vector<int> a={1,2,3,4,5,6,7}; N* root=build(a,0,(int)a.size()-1); cout << "In-order: "; in(root); cout << "\nHeight: " << h(root); } -
Check if two BSTs contain identical sets of values (using in-order).দুটি BST-তে একই set আছে কিনা পরীক্ষা করুন (in-order মিলিয়ে)।
✨ Show Answer
identical_bst.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N* ins(N* r,int x){if(!r)return new N(x); if(x<r->v) r->l=ins(r->l,x); else if(x>r->v) r->r=ins(r->r,x); return r;} void collect(N* r, vector<int>& v){ if(!r)return; collect(r->l,v); v.push_back(r->v); collect(r->r,v); } int main(){ N* a=nullptr; for(int x:{5,3,8,1}) a=ins(a,x); N* b=nullptr; for(int x:{8,3,1,5}) b=ins(b,x); vector<int> va,vb; collect(a,va); collect(b,vb); cout << (va==vb ? "Identical" : "Different"); } -
Recover a BST in which exactly two nodes have been swapped.একটি BST-তে দুটি node ভুলে swap হয়ে গেছে — সেগুলো খুঁজে ঠিক করুন।
✨ Show Answer
recover.cpp#include <bits/stdc++.h> using namespace std; struct N{int v;N*l,*r;N(int x):v(x),l(nullptr),r(nullptr){}}; N *first=nullptr,*second=nullptr,*prev=nullptr; void in(N* r){ if(!r) return; in(r->l); if(prev && prev->v > r->v){ if(!first) first=prev; second=r; } prev=r; in(r->r); } int main(){ // Correct BST: 1 2 3 4 5 ; here we swap 2 and 4 in-place N* root=new N(3); root->l=new N(4); root->r=new N(5); root->l->l=new N(1); root->l->r=new N(2); in(root); if(first && second) swap(first->v, second->v); // verify prev=nullptr; first=second=nullptr; in(root); cout << (first==nullptr ? "Recovered OK" : "Still broken"); }
Summary — Module 17
A BST keeps a single invariant — left < node < right — and that single rule unlocks search, insert, and delete in average O(log n). The in-order traversal of a BST is always sorted, which gives us elegant solutions for k-th smallest, range sum, validation, and recovery. The catch: naive BSTs degenerate on sorted input. Module 18 fixes that with self-balancing trees.