【分治法】最接近点对问题 C++(附代码分析及实例)

问题描述

给定平面上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中的所有点,对排好序的点列做一次扫描,就可以找出所有可能构成最接近点对的点

代码

数据结构

实现了两个类PointXPointY,存储点的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)n4O(1)n<4
T(n)=O(nlogn)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值