B-Trees, B+ Trees & Segment Trees Intro
B-Tree, B+ Tree ও Segment Tree (intro)
1. Why Disks Love Wide Trees
A disk read fetches a whole page (typically 4 KB or 16 KB) — even if you only need 8 bytes. Reading from disk is ~10⁵× slower than RAM. So if our tree has only two children per node, every level costs one disk page; an AVL/RB tree with millions of keys becomes ~25 disk reads per lookup.
Solution: pack hundreds of keys into each node. A B-tree of order m has up to m children per node; a tree of n keys has height ≈ log_m(n). With m = 200, a billion keys needs only 4 levels — 4 disk reads instead of 30.
2. B-Tree vs B+ Tree
| Aspect | B-Tree | B+ Tree |
|---|---|---|
| Data location | Internal nodes and leaves | Leaves only |
| Internal node use | Holds keys + data + child pointers | Index only — pure routing |
| Leaf links | Independent | Linked left ↔ right (linked-list) |
| Range scan | Slower (must traverse) | Trivial — walk leaf list |
| Used by | Some filesystems | PostgreSQL, MySQL/InnoDB, SQLite, Oracle |
3. The Segment Tree — Range Queries on RAM
For in-memory range queries (sum/min/max over [l, r]) on an array, the right structure is a segment tree: a complete binary tree where each leaf holds an element and each internal node holds the aggregate of its subtree's range. Build is O(n); query and point-update are O(log n).
#include <bits/stdc++.h>
using namespace std;
struct SegTree {
int n;
vector<long long> t;
SegTree(vector<int>& a) : n(a.size()), t(4*a.size(), 0) { build(1, 0, n-1, a); }
void build(int node, int l, int r, vector<int>& a) {
if (l == r) { t[node] = a[l]; return; }
int m = (l + r) / 2;
build(2*node, l, m, a);
build(2*node+1, m+1, r, a);
t[node] = t[2*node] + t[2*node+1];
}
long long query(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return t[node];
int m = (l + r) / 2;
return query(2*node, l, m, ql, qr) + query(2*node+1, m+1, r, ql, qr);
}
void update(int node, int l, int r, int pos, int val) {
if (l == r) { t[node] = val; return; }
int m = (l + r) / 2;
if (pos <= m) update(2*node, l, m, pos, val);
else update(2*node+1, m+1, r, pos, val);
t[node] = t[2*node] + t[2*node+1];
}
};
int main() {
vector<int> a = {1, 3, 5, 7, 9, 11};
SegTree st(a);
cout << "sum[1..3] = " << st.query(1, 0, st.n-1, 1, 3) << "\n"; // 3+5+7=15
st.update(1, 0, st.n-1, 2, 100);
cout << "after a[2]=100, sum[0..5] = " << st.query(1, 0, st.n-1, 0, 5);
}
4. When To Use Which
B / B+ Tree
- Data lives on disk or SSD
- Range scans matter (B+)
- Database indexes, filesystem metadata
Segment Tree
- In-memory array
- Need range sum/min/max with updates
- Competitive programming, real-time analytics
5. Practice Problems
-
A B-tree of order m holding n keys has height O(log_m n). Compute the height for m = 200 and n = 10⁹.order m=200, n=10⁹ — B-tree-এর height কত?
✨ Show Answer (উত্তর দেখুন)
Answer: log₂₀₀(10⁹) = 9 / log₁₀(200) ≈ 9 / 2.3 ≈ 3.9. So 4 disk pages per lookup. With a binary tree, log₂(10⁹) ≈ 30 — almost 8× slower on disk.
-
Modify the segment tree above to compute range MIN instead of range SUM.SUM-এর বদলে MIN return করতে segment tree পরিবর্তন করুন।
✨ Show Answer (উত্তর দেখুন)
Change: initialise
twithLLONG_MAX, replace+withmin(...), query identity returnsLLONG_MAX. The skeleton stays identical. -
Range MAX with point updates on the array
[2, 1, 5, 3, 4, 8, 6]: print the answer for query [2..5].[2,1,5,3,4,8,6] — range max[2..5] = ?✨ Show Answer (উত্তর দেখুন)
a3.cpp#include <bits/stdc++.h> using namespace std; int n; vector<int> t; void build(int nd, int l, int r, vector<int>& a) { if (l==r) { t[nd]=a[l]; return; } int m=(l+r)/2; build(2*nd,l,m,a); build(2*nd+1,m+1,r,a); t[nd] = max(t[2*nd], t[2*nd+1]); } int qry(int nd, int l, int r, int ql, int qr) { if (qr<l||r<ql) return INT_MIN; if (ql<=l&&r<=qr) return t[nd]; int m=(l+r)/2; return max(qry(2*nd,l,m,ql,qr), qry(2*nd+1,m+1,r,ql,qr)); } int main() { vector<int> a = {2,1,5,3,4,8,6}; n = a.size(); t.assign(4*n, INT_MIN); build(1, 0, n-1, a); cout << qry(1, 0, n-1, 2, 5); } -
Why does PostgreSQL use B+ tree (not B-tree, not Red-Black) for its default index?PostgreSQL default index B+ tree কেন?
✨ Show Answer (উত্তর দেখুন)
Answer: (1) Disk-friendly: each node = one page → minimal disk reads. (2) Range scans:
WHERE created_at BETWEEN ...is the most common database query — leaf-level linked list makes this O(matches). (3) Bulk loading: B+ tree allows efficient sorted bulk-insert. RB tree is in-memory only. -
Compute prefix sums in O(n) and answer range-sum queries in O(1) — when does this beat a segment tree?Prefix sum দিয়ে কখন segment tree-কেও হারানো যায়?
✨ Show Answer (উত্তর দেখুন)
Answer: when the array is static (no updates). Prefix sum gives O(1) queries with O(n) memory — beats segment tree's O(log n). With updates, each update needs O(n) to refresh prefix sums; segment tree's O(log n) wins.
-
Find the index of the first element ≥ x inside the segment tree's range, in O(log n) — sketch the descent.Segment tree-এ "≥ x প্রথম element-এর index" কীভাবে O(log n)-এ?
✨ Show Answer (উত্তর দেখুন)
Sketch: store the max in each node. Descend from the root: if the left child's max ≥ x, go left; else go right. The recursion depth is O(log n). This is the "descend on segment tree" pattern — used in many competitive problems (e.g. position to insert / first true in a binary array).
Summary — Module 19
B / B+ trees trade extra width for fewer disk seeks — the foundation of every major database index. Segment trees bring range queries with point updates to O(log n) in RAM — the foundation of competitive-programming range work, with full lazy-prop details coming in Module 23.