Concept units

KD-trees: nearest neighbors fast

Intermediate

Read these first if they are new to you

The idea in one paragraph

A KD-tree sorts points into boxes inside boxes. It cuts the space in two at the middle point along x, so that half the points lie on each side. Then it cuts each half along y, and so on, until every point has its own small box. To find a query's nearest neighbor, the search goes first to the box that holds the query, which gives a good candidate. Then it backs out and looks into a neighboring box only if that box could hold something closer. Most boxes fail that test, so the search skips them and every point inside them. For a hundred thousand points in 3D, a search checks a few dozen points instead of all of them.

Splitting at the middle point builds boxes inside boxes

A KD-tree, short for k-dimensional tree, is built by a rule that repeats:

  1. Sort the points by one coordinate, starting with x.
  2. Take the median point, the one in the middle of the sorted list. A line through it, across that coordinate, splits the points into two halves, one on each side of the line.
  3. Repeat on each half with the next coordinate (y after x, then x again in 2D), until no points are left.

Each median point becomes a node of a tree. The first is the root; the two halves become its two branches. Each node owns a box: the region of space its branch covers.

Worked example, part 1: build a tree for seven points

The points are P1 (1, 2), P2 (3, 7), P3 (4, 4), P4 (6, 1), P5 (7, 6), P6 (8, 3) and P7 (9, 8).

  1. Sorted by x: 1, 3, 4, 6, 7, 8, 9. The median is P4, so the root splits at x = 6.
  2. Left half {P1, P2, P3}, sorted by y: 2, 4, 7. P3 splits it at y = 4. P1 goes below, P2 above.
  3. Right half {P5, P6, P7}, sorted by y: 3, 6, 8. P5 splits it at y = 6. P6 goes below, P7 above.
  4. P1, P2, P6 and P7 are each alone in a box. Each still cuts its box along x, the next coordinate, but both sides of that cut are empty, so the building stops.
The same seven points as boxes and as a tree
Each cut splits a box at its middle point. Each side gets half the points, not half the space. Blue lines split on x, green lines split on y. The label on each branch says which side of its parent's cut it holds: P5's branch holds the right half, x ≥ 6.

In 2D and 3D the search checks a few dozen points, not all of them

The table counts the distances that the code above measured per query, averaged over 200 random queries among random points spread evenly in a cube. Every answer matched brute force.

PointsChecked in 2DChecked in 3DChecked in 10D
1,0001626751
10,00019332,965
100,00023375,508

In 2D and 3D, a hundred times more points adds only a handful of checks. The tree has about log₂ N levels, the number of times N can be halved before reaching 1: about 17 for 100,000 points. A typical search visits a few nodes per level. Building the tree costs more than a query. A good build finds each median without a full sort and takes about N × log₂ N steps: for 100,000 points, as many as 17 brute-force queries. The code above sorts again at every level, so its build costs somewhat more. So a tree pays off once you ask more than a few dozen queries of the same points.

Where KD-trees stop helping

In 10 dimensions the same search checked thousands of points, and in hundreds of dimensions it checks nearly all of them. The reason is that in many dimensions, almost every box lies close to the query along some axis. Clumped or unusual data can also make a search slower than the table suggests. Real libraries differ from this page in details: they stop splitting when a box holds a few points, they may cut at the middle of a box instead of at a point, and they test the distance to a whole box, not only to one split line, which skips a few more boxes.

Check yourself

  1. Your turn to build: the points are (2, 6), (5, 2), (8, 5), (1, 1) and (6, 8). Which point is the root, and which points does each half get?

    Answer

    Sorted by x: 1, 2, 5, 6, 8. The root is (5, 2), split at x = 5. The left half gets (1, 1) and (2, 6); the right half gets (6, 8) and (8, 5). Each half is then split on y.

  2. During a search, the best squared distance so far is 2.25 (distance 1.5). The query is 1.2 away from the split line of a branch it has not visited. Can the search skip that branch?

    Answer

    No. 1.2² = 1.44 is less than 2.25, so the other side of the line comes within the best distance and might hold a closer point. The search must look.

  3. In the widget, put the query in the middle of a crowded area, then near a corner far from the points. When does the search check more points, and why?

    Answer

    Usually far from the points. There, the best distance stays large, so the circle crosses many split lines and fewer boxes can be skipped. Near crowded points, the best distance shrinks fast.

Go further

Optional background.

Where this shows up

No unit or explanation builds on this one yet.