A binary space partition of -dimensional points: at each level, split by the median along one axis (cycling through the axes, or choosing the widest spread).
Construction —
struct Node { Point p; int l = -1, r = -1; int x1, y1, x2, y2; }; // bounding box
vector<Node> t;
int build(int lo, int hi, int depth) { // pts[lo, hi)
if (lo >= hi) return -1;
int mid = (lo + hi) / 2;
nth_element(pts.begin() + lo, pts.begin() + mid, pts.begin() + hi,
[&](const Point& a, const Point& b) { return depth % 2 ? a.y < b.y : a.x < b.x; });
int cur = t.size(); t.push_back({pts[mid]});
t[cur].l = build(lo, mid, depth + 1);
t[cur].r = build(mid + 1, hi, depth + 1);
pullBoundingBox(cur);
return cur;
}nth_element is per level, so the build is — better than sorting at every level.
Queries
Nearest neighbour
Descend into the child containing the query point first, then check whether the other side’s bounding box could contain anything closer than the current best. If not, prune.
void nearest(int node, Point q) {
if (node == -1 || boxDist(node, q) >= best) return; // PRUNE
best = min(best, dist(t[node].p, q));
int first = ..., second = ...; // nearer child first
nearest(first, q);
nearest(second, q);
}Expected for random points in low dimensions; worst case.
Range (rectangle) query
Fully-contained subtrees are answered from a precomputed aggregate; disjoint subtrees are pruned; partially overlapping ones are recursed into.
Complexity summary
| Operation | 2D | General |
|---|---|---|
| Build | ||
| Rectangle query | ||
| Nearest neighbour | expected | degrades badly with |
| Insert (unbalanced) | ||
| Memory |
The curse of dimensionality
Beyond -, KD-tree nearest-neighbour search degenerates to a linear scan, because the pruning bound almost never fires. For high dimensions use locality-sensitive hashing or approximate methods (HNSW), or accept the linear scan.
KD-tree vs the alternatives
| Structure | Rectangle query | NN | Updates | Notes |
|---|---|---|---|---|
| KD-tree | ✔ | ✔ (rebuild periodically) | memory, general | |
| 2D BIT | ✘ | ✔ | needs a small dense grid | |
| Persistent segment tree | ✘ | ✘ | offline-built, online query | |
| Offline BIT sweep | am. | ✘ | ✔ | usually the best if offline |
| Range tree | ✘ | ✘ | memory | |
| Quadtree / R-tree | varies | ✔ | ✔ | better for spatial data with extent |
In competitive programming, an offline sweep with a BIT beats a KD-tree whenever the queries can be sorted. The KD-tree earns its place when queries are online and geometric (nearest neighbour, or non-rectangular regions).
Keeping it balanced under insertion
A plain KD-tree degrades as points are inserted. Two standard fixes:
- Periodic rebuild — rebuild the whole tree every insertions, or rebuild any subtree that becomes too unbalanced (the scapegoat approach).
- Logarithmic method — keep static KD-trees of sizes and merge on overflow. amortized insertion, trees to query.
Uses
- Nearest neighbour / nearest neighbours
- “Points inside a rectangle”, “points inside a circle”
- Collision detection and ray tracing (though a BVH is usually better for those)
- -NN classification in machine learning
- Closest pair — though divide and conquer is cleaner
See also: Other Geometric Structures · Closest Pair · 2D Queries