问题描述
给定平面上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小
问题分析
先考虑一下一维
情况下,取中间某个点m,将所有点划分为两个集合,递归的找出左右集合的最接近点对,最后再和最靠近点m的左右两点间的距离作比较,最小的就是整个点对中最接近的
现在将一维的情况扩展到二维
,二维比一维复杂的地方在于每个点都有两个坐标,我们用一条直线l
将平面上的所有点同样分成两个集合,再递归的去两个集合中找最接近的点对,可以得到左右两个最近距离,取它们俩较小的值作为d
,用于后面的寻找。
直线l如何确定呢,我们选择所有点的x坐标的中位数作为l的x坐标,这样可以将大问题分成左右两个规模大致相同的子问题
找到左右两个子集的最接近距离后,再找跨区域的点对。乍一看,似乎每个点都要和对面的每一个点进行一次计算,但实际上,我们只需要考虑距离直线l小于d的点,大于d的点和对面区域的距离显然大于d。
- 考虑S1中任意一点p,它如果有可能与S2中的点q构成最接近点对,则必有distance(p,q)<d。满足这个条件的S2中的点一定落在一个d×2d的矩形R中
- 由d的意义可知,S2中任何两个S中的点的距离都不可能小于d
- 因此,在分治法的合并步骤中,最多只需要检查6xn/2=3n个候选者
证明为什么每个点只需要检查6个点
将矩形R的长为2d的边三等分,将它的长为d的边2等分,由此导出6个(d/2)x(2d/3)的矩形。若矩形R中有2个以上S中的点。设u、v是位于同一小矩形中的两个点,则distance(u,v)<d,这与d的意义相矛盾
为了确切的知道是哪6个点,可以将p和S2中所有的点投影到直线l上。由于能与p点一起构成最接近点对的点一定在矩阵R中,所以它们在直线l上的投影点距p的投影点的距离小于d。
因此,若将S1和S2中所有点按其y坐标排好序,则对S1中的所有点,对排好序的点列做一次扫描,就可以找出所有可能构成最接近点对的点
代码
数据结构
实现了两个类PointX
和PointY
,存储点的x、y坐标,PointY
类额外使用成员变量p
来对应该点在X中的下标。重载了<=运算符,用于按x和按y排序
class PointX {
public:
int operator<=(const PointX a) const {
return x <= a.x;
}
public:
float x, y;
};
class PointY {
public:
int operator<=(const PointY a) const {
return y <= a.y;
}
public:
int p;
float x, y;
};
Cpair函数
main函数直接调用,进行预处理(排序、设置p的对应关系)后调用closest函数
进行计算
bool Cpair(PointX X[], int n, PointX& a, PointX& b, float &d) {
if (n < 2) {
return false;//没有点对
}
MergeSort(X, n);//按x排序
PointY* Y = new PointY[n];
for (int i = 0; i < n; i++)
{
Y[i].p = i;//记录Y中每个点的原始位置
Y[i].x = X[i].x;
Y[i].y = X[i].y;
}
MergeSort(Y, n);//按y排序
PointY* Z = new PointY[n];
closest(X, Y, Z, 0, n - 1, a, b, d);
delete[] Y;
delete[] Z;
return true;
}
closest函数
void closest(PointX X[], PointY Y[], PointY Z[], int l, int r, PointX& a, PointX& b, float& d) {
if (r - l == 1) {
a = X[l];
b = X[r];
d = distance(X[l], X[r]);
return;
}
if (r - l == 2) {
float d1 = distance(X[l], X[l + 1]);
float d2 = distance(X[l+1], X[r]);
float d3 = distance(X[l], X[r]);
if (d1 <= d2 && d1 <= d3) {
a = X[l];
b = X[l + 1];
d = d1;
return;
}
if (d2 <= d3) {
a = X[l];
b = X[r];
d = d2;
}
else {
a = X[l];
b = X[r];
d = d3;
}
return;
}
//多于三个点,分治法
int m = (l + r) / 2;
int f = l, g = m + 1;
for (int i = l; i <= r; i++) {
if (Y[i].p > m)
Z[g++] = Y[i];
else
Z[f++] = Y[i];
}
closest(X, Z, Y, l, m, a, b, d);
float dr;
PointX ar, br;
closest(X, Z, Y, m + 1, r, ar, br, dr);
if (dr < d) {
a = ar;
b = br;
d = dr;
}
//重构数组Y
Merge(Z, Y, l, r);//把Z合并到Y中
int k = l;
for (int i = l; i <= r; i++) {
if (fabs(X[m].x - Y[i].x) < d)
Z[k++] = Y[i];
}
for (int i = l; i < k; i++) {
for (int j = i + 1; j < k && Z[j].y - Z[i].y < d; j++) {
float dp = distance(Z[i], Z[j]);
if (dp < d) {
d = dp;
a = X[Z[i].p];
b = X[Z[j].p];
}
}
}
}
在这个函数中,首先判断点对的个数,三个及以内就直接计算两两之间的距离,取最小即可。
如果点数大于三,就用分治法,先确定直线l的索引m,再递归的求左右两个区间的最短距离,选最小的作为d
int k = l;
for (int i = l; i <= r; i++) {
if (fabs(X[m].x - Y[i].x) < d)
Z[k++] = Y[i];
}
这一步选出位于分割线(中线)附近、可能形成更近点对的候选点,将这些候选点存储在Z中。X[m].x
就是中位线的横坐标
for (int i = l; i < k; i++) {
for (int j = i + 1; j < k && Z[j].y - Z[i].y < d; j++) {
float dp = distance(Z[i], Z[j]);
if (dp < d) {
d = dp;
a = X[Z[i].p];
b = X[Z[j].p];
}
}
}
上面这一步用于在候选点中寻找距离更近的点对。
外层循环:遍历每个候选点Z[i]。
内层循环:对每个Z[i],仅检查其后满足y坐标差小于d的点Z[j]。
若找到比当前d更小的距离dp,更新最小距离和对应点对。
这样最终的d
值就是最接近点对的距离
完整实例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
class PointX {
public:
int operator<=(const PointX a) const {
return x <= a.x;
}
public:
float x, y;
};
class PointY {
public:
int operator<=(const PointY a) const {
return y <= a.y;
}
public:
int p;
float x, y;
};
inline float distance(PointX &a, PointX &b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline float distance(PointY& a, PointY& b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
void Merge(PointX Y[], int m, int n) {
int i = 0, j = m, k = 0;
PointX* temp = new PointX[n];
while (i < m && j < n) {
if (Y[i] <= Y[j]) {
temp[k++] = Y[i++];
}
else {
temp[k++] = Y[j++];
}
}
while (i < m) {
temp[k++] = Y[i++];
}
while (j < n) {
temp[k++] = Y[j++];
}
for (int i = 0; i < n; i++) {
Y[i] = temp[i];
}
delete[] temp;
}
void Merge(PointY X[], int m, int n) {
int i = 0, j = m, k = 0;
PointY* temp = new PointY[n];
while (i < m && j < n) {
if (X[i] <= X[j]) {
temp[k++] = X[i++];
}
else {
temp[k++] = X[j++];
}
}
while (i < m) {
temp[k++] = X[i++];
}
while (j < n) {
temp[k++] = X[j++];
}
for (int i = 0; i < n; i++) {
X[i] = temp[i];
}
delete[] temp;
}
//重构数组Y
void Merge(PointY Z[], PointY Y[], int l, int r) {
for (int i = l; i <= r; i++)
Y[i] = Z[i];
}
void MergeSort(PointX X[], int n) {
if (n < 2) {
return;
}
int m = n / 2;
MergeSort(X, m);
MergeSort(X + m, n - m);
Merge(X, m, n);
}
void MergeSort(PointY Y[], int n) {
if (n < 2) {
return;
}
int m = n / 2;
MergeSort(Y, m);
MergeSort(Y + m, n - m);
Merge(Y, m, n);
}
void closest(PointX X[], PointY Y[], PointY Z[], int l, int r, PointX& a, PointX& b, float& d) {
if (r - l == 1) {
a = X[l];
b = X[r];
d = distance(X[l], X[r]);
return;
}
if (r - l == 2) {
float d1 = distance(X[l], X[l + 1]);
float d2 = distance(X[l+1], X[r]);
float d3 = distance(X[l], X[r]);
if (d1 <= d2 && d1 <= d3) {
a = X[l];
b = X[l + 1];
d = d1;
return;
}
if (d2 <= d3) {
a = X[l];
b = X[r];
d = d2;
}
else {
a = X[l];
b = X[r];
d = d3;
}
return;
}
//多于三个点,分治法
int m = (l + r) / 2;
int f = l, g = m + 1;
for (int i = l; i <= r; i++) {
if (Y[i].p > m)
Z[g++] = Y[i];
else
Z[f++] = Y[i];
}
closest(X, Z, Y, l, m, a, b, d);
float dr;
PointX ar, br;
closest(X, Z, Y, m + 1, r, ar, br, dr);
if (dr < d) {
a = ar;
b = br;
d = dr;
}
//重构数组Y
Merge(Z, Y, l, r);//把Z合并到Y中
int k = l;
for (int i = l; i <= r; i++) {
if (fabs(X[m].x - Y[i].x) < d)
Z[k++] = Y[i];
}
for (int i = l; i < k; i++) {
for (int j = i + 1; j < k && Z[j].y - Z[i].y < d; j++) {
float dp = distance(Z[i], Z[j]);
if (dp < d) {
d = dp;
a = X[Z[i].p];
b = X[Z[j].p];
}
}
}
}
bool Cpair(PointX X[], int n, PointX& a, PointX& b, float &d) {
if (n < 2) {
return false;//没有点对
}
MergeSort(X, n);//按x排序
PointY* Y = new PointY[n];
for (int i = 0; i < n; i++)
{
Y[i].p = i;//记录Y中每个点的原始位置
Y[i].x = X[i].x;
Y[i].y = X[i].y;
}
MergeSort(Y, n);//按y排序
PointY* Z = new PointY[n];
closest(X, Y, Z, 0, n - 1, a, b, d);
delete[] Y;
delete[] Z;
return true;
}
int main() {
int n;
cin >> n;
PointX* X = new PointX[n];
for (int i = 0; i < n; i++) {
cin >> X[i].x >> X[i].y;
}
PointX a, b;
float d=1.0;
if (Cpair(X, n, a, b, d)) {
cout << a.x << " " << a.y << endl;
cout << b.x << " " << b.y << endl;
cout << "distance=" << d << endl;
}
else {
cout << "No pair found." << endl;
}
delete[] X;
return 0;
}
测试样例
输入
输出
复杂度分析
T
(
n
)
=
{
2
T
(
n
/
2
)
+
O
(
n
)
n
≥
4
O
(
1
)
n
<
4
T(n)=\Big\{^{O(1) \quad n<4}_{2T(n/2)+O(n) \quad n≥ 4}
T(n)={2T(n/2)+O(n)n≥4O(1)n<4
T(n)=O(nlogn)