Closest pair of points

Introduction

Given nn points in the two-dimensional plane, find a pair of points with minimum Euclidean distance.

We introduce a divide-and-conquer algorithm with time complexity O(nlog⁡n)O(n\log n) for this problem. The algorithm was proposed by Franco P. Preparata in 1975, and Preparata and Michael Ian Shamos proved that it is optimal in the decision-tree model.

Procedure

As in a conventional divide-and-conquer algorithm, we split the set of nn points into two equally sized sets S1,S2S_1, S_2 and recurse. However, we face a difficulty: how should we merge? In other words, how do we find the closest pair with one point in S1S_1 and the other in S2S_2? For now, assume that merging takes O(n)O(n) time; then the total complexity is T(n)=2T(n2)+O(n)=O(nlog⁡n)T(n) = 2T(\frac{n}{2}) + O(n) = O(n\log n).

First sort all points by xix_i as the primary key and yiy_i as the secondary key, use point pm(m=⌊n2⌋)p_m (m = \lfloor \frac{n}{2} \rfloor) as the dividing point, and split the point set into A1,A2A_1,A_2:

A1={pi ∣ i=0…m}A2={pi ∣ i=m+1…n−1}\begin{aligned} A_1 &= \{p_i \ \big | \ i = 0 \ldots m \}\\ A_2 &= \{p_i \ \big | \ i = m + 1 \ldots n-1 \} \end{aligned}

Recurse to find the closest pair within each point set. Let their distances be h1,h2h_1,h_2, and let hh be the smaller one.

Now we merge. We seek a pair of points, one in A1A_1 and one in A2A_2, whose distance is less than hh. Therefore, every point whose horizontal distance from xmx_m is less than hh is put into set BB:

B={pi ∣ ∣xi−xm∣<h}B = \{ p_i \ \big | \ \lvert x_i - x_m \rvert < h \}

As shown in the figure, line mm divides the points into two parts: the points to the left of mm form A1A_1, and those to the right form A2A_2.

Applying the rule B={pi ∣ ∣xi−xm∣<h}B = \{ p_i \ \big | \ \lvert x_i - x_m \rvert < h \} gives the set BB formed by the green points.
nearest-points1
nearest-points1

For every point in BB, say pip_i, our goal is to find another point also in BB whose distance from it is less than hh. To avoid considering each pair twice, we consider only points whose vertical coordinate is less than yiy_i. Clearly, for a valid point pjp_j, yi−yjy_i - y_j must be less than hh. Thus we obtain the set C(pi)C(p_i):

C(pi)={pj ∣ pj∈B, yi−h<yj≤yi}C(p_i) = \{ p_j\ \big |\ p_j \in B,\ y_i - h < y_j \le y_i \}

Choose a point in BB, say pip_i. Applying the rule C(pi)={pj ∣ pj∈B, yi−h<yj≤yi}C(p_i) = \{ p_j\ \big |\ p_j \in B,\ y_i - h < y_j \le y_i \} gives the set CC consisting of the yellow points inside the red box.

nearest-points2
nearest-points2

If we sort the points in BB by yiy_i, then C(pi)C(p_i) is easy to obtain: it is just the consecutive points immediately adjacent to pip_i.

We therefore obtain the merge steps:

  1. Construct set BB.
  2. Sort the points in BB by yiy_i. The usual approach takes O(nlog⁡n)O(n\log n), but we can change the strategy and optimize it to O(n)O(n) (as explained below).
  3. For each pi∈Bp_i \in B, consider pj∈C(pi)p_j \in C(p_i); compute the distance for every pair (pi,pj)(p_i,p_j) and update the answer (the closest pair in the current set).

Notice that sorting was mentioned twice above. Since the point coordinates never change, the first sort needs to be performed only once, before the divide-and-conquer begins. Let each recursive call return its current point set sorted by yiy_i; for the second sort, the upper level can directly merge the two point sets already sorted by the lower level.

The algorithm still seems inefficient: ∣C(pi)∣|C(p_i)| could be O(n)O(n), which would make the total complexity too large. In fact, this is not the case: its maximum size is 77. We prove this as follows:

Complexity proof

We know that every point in C(pi)C(p_i) has vertical coordinate in the range (yi−h,yi](y_i-h,y_i]; moreover, all points in C(pi)C(p_i) and pip_i itself have horizontal coordinates in (xm−h,xm+h)(x_m-h,x_m+h). Together they form a rectangle of size 2h×h2h \times h.

Split this rectangle into two h×hh \times h squares. Excluding pip_i, the points in one square form C(pi)∩A1C(p_i) \cap A_1, and those in the other form C(pi)∩A2C(p_i) \cap A_2; the distance between any two points within either square is greater than hh (because they come from the same lower-level recursive call).

Split an h×hh \times h square into four h2×h2\frac{h}{2} \times \frac{h}{2} small squares. Each small square contains at most 11 point: the greatest possible distance between two points in it is the diagonal length, h2\frac{h}{\sqrt 2}, which is less than hh.

nearest-points3
nearest-points3

Thus each square contains at most 44 points, the rectangle contains at most 88 points, and after removing pip_i itself, max⁡(C(pi))=7\max(C(p_i))=7.

Reference implementation
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

const int N = 500000 + 10;

struct point {
  int x, y, id;
};

int n, A, B;
point a[N];
// mindist is the squared closest distance
long long mindist;

// Update the answer
void upd_ans(const point& a, const point& b) {
  long long dist =
      1LL * (a.x - b.x) * (a.x - b.x) + 1LL * (a.y - b.y) * (a.y - b.y);
  if (dist < mindist) {
    mindist = dist;
    A = a.id;
    B = b.id;
  }
}

// Use [l, r) to represent the current divide-and-conquer interval
void DC(int l, int r) {
  // The current interval contains only one point; return directly
  if (l + 1 == r) return;

  int m = (l + r) >> 1;
  int midx = a[m].x;
  DC(l, m);
  DC(m, r);
  // Use std::inplace_merge() to merge-sort
  inplace_merge(a + l, a + m, a + r,
                [&](point a, point b) { return a.y < b.y; });

  vector<point> t;
  for (int i = l; i < r; i++)
    // Compare squared distances, and do not include equality
    if (1LL * (a[i].x - midx) * (a[i].x - midx) < mindist) t.push_back(a[i]);
  for (int i = 0; i < t.size(); i++)
    for (int j = i + 1; j < t.size(); j++) {
      if (1LL * (t[i].y - t[j].y) * (t[i].y - t[j].y) >= mindist) break;
      upd_ans(t[i], t[j]);
    }
}

void Solve() {
  scanf("%d", &n);
  for (int i = 0; i < n; i++) {
    scanf("%d %d", &a[i].x, &a[i].y);
    a[i].id = i;
  }
  // Sort by horizontal coordinate before calling
  sort(a, a + n, [&](point x, point y) { return x.x < y.x; });
  mindist = 9'000'000'000'000'000'000LL;
  DC(0, n);
  printf("%d %d\n", A, B);
}

int main() {
  int T;
  scanf("%d", &T);
  while (T--) Solve();
  return 0;
}

Extension: minimum-perimeter triangle in the plane

The algorithm above has an interesting extension: choose three points from a given set so that the sum of their three pairwise distances is minimized.

The algorithm is largely unchanged. At each step, try to find a triangle whose perimeter is smaller than the current answer dd. Put all points whose horizontal distance from xmx_m is less than d2\frac{d}{2} into BB and try to update the answer. (A triangle with perimeter dd has a longest side shorter than d2\frac{d}{2}.)

Non-divide-and-conquer algorithm

Besides the divide-and-conquer algorithm described above, there is another algorithm with the same time complexity, O(nlog⁡n)O(n \log n), that does not use divide and conquer.

We can use a common idea from sequence processing: for each element, add its contribution with all elements to its left to the answer. The closest-pair problem in the plane can be handled in the same way.

Specifically, sort all points by xix_i as the primary key and yiy_i as the secondary key, and build a multiset keyed by yiy_i. At each position ii, perform the following operations:

  1. Remove from the set every point satisfying xi−xj≥dx_i - x_j \ge d. Such points can no longer contribute to the answer.
  2. For every point in the set satisfying ∣yi−yj∣<d\lvert y_i - y_j \rvert < d, compute its distance from pip_i.
  3. Insert pip_i into the set.

Because each point is inserted and removed at most once, inserting and removing points takes O(nlog⁡n)O(n \log n) time. The time-complexity proof for counting the answer is similar to the proof for the divide-and-conquer algorithm, and the reader may try it as an exercise.

Reference implementation
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <set>
using namespace std;

constexpr int N = 500000 + 10;

struct point {
  int x, y, id;

  point() {}

  bool operator<(const point& a) const {
    return x < a.x || (x == a.x && y < a.y);
  }
};

int n, A, B;
long long mindist;
point a[N];

void upd_ans(const point& a, const point& b) {
  long long dist =
      1LL * (a.x - b.x) * (a.x - b.x) + 1LL * (a.y - b.y) * (a.y - b.y);
  if (dist < mindist) {
    mindist = dist;
    A = a.id;
    B = b.id;
  }
}

void Solve() {
  scanf("%d", &n);
  for (int i = 0; i < n; i++) {
    scanf("%d %d", &a[i].x, &a[i].y);
    a[i].id = i;
  }
  sort(a, a + n);
  multiset<pair<double, point>> s;
  mindist = 9'000'000'000'000'000'000LL;
  for (int i = 0, l = 0; i < n; i++) {
    for (; l < i && 1LL * (a[i].x - a[l].x) * (a[i].x - a[l].x) >= mindist; l++)
      s.erase(s.find({a[l].y, a[l]}));
    // Be careful about floating-point errors
    for (auto it =
             s.lower_bound({(double)a[i].y - sqrt(mindist) + 1e-6, point()});
         it != s.end() &&
         1LL * (it->first - a[i].y) * (it->first - a[i].y) < mindist;
         it++)
      upd_ans(it->second, a[i]);
    s.insert({a[i].y, a[i]});
  }
  printf("%d %d\n", A, B);
}

int main() {
  int T;
  scanf("%d", &T);
  while (T--) Solve();
  return 0;
}

Expected-linear algorithm

Besides the O(nlog⁡n)O(n \log n) method above, there is also an algorithm with expected complexity O(n)O(n).

First shuffle the points randomly, and maintain the answer for the prefix point set. From the first i−1i - 1 points, consider finding the answer for point ii.

Let the closest-pair distance among the first i−1i - 1 points be ss. Partition the plane into a grid of cells with side length ss, and store the points in each cell using a hash table. Then inspect all points in the nine cells surrounding the cell containing the ii-th point and update the answer. Only O(1)O(1) points need to be inspected: because the closest-pair distance among the first i−1i - 1 points is ss, each cell contains at most 44 points.

If the answer is updated during this process, rebuild the grid; otherwise, do not rebuild it. Among the first ii points, the probability that the closest pair contains ii is O(1i)O\left(\frac{1}{i}\right), while rebuilding the grid costs O(i)O(i), so the expected cost for the ii-th point is O(1)O(1). Thus for nn points, the expected complexity is O(n)O(n).

Exercises

References and further reading

The divide-and-conquer section of this page is primarily translated from the blog post Нахождение пары ближайших точек and its English translation Finding the nearest pair of points. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.

Zhihu column: Computational geometry — the closest-pair problem

This content comes from OI Wiki: Closest pair of points. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.