分治法求平面最近点对

题意即为平面上有n个点,求距离最近的两个点,很显然,暴力求解是n^2的。
那么可以利用分治法把时间复杂度降到nlognlogn
当然,利用归并排序时可以降到nlogn的,不过显然我是写不来归并排序的

原理是这样的,先把平面上的点按x,y的顺序排序//nlogn
然后再找下标为中间的点,把平面上的点均匀两等分
然后最小点对一定是处于左边最小,右边最小和跨边界最小。
跨边界最小该如何求解呢?

我们可以先把左右两边的最小值求出来,设为m
然后去找和中心点x轴相差m以内的点//O(n)
然后再把这个集合的点按y轴sort,老师说这个集合的点最坏情况下是n个元素的。//nlogn
但是每个点去找y方向上距离小于前面m的点,统计学已经证明出来是7个。
然后去枚举每一个距离,时间复杂度是7n
O(n) + O(n) + O(logn) = logn

所以时间复杂度递推公式为
T(N) = 2T(N/2) + nlogn
根据主定理,时间复杂度为nlognlogn
(如果题目数据范围是1e6,就去写归并排序吧,1e5的话就这样懒一点好了。。。)
代码如下

#include <bits/stdc++.h>
using namespace std;
#define N (int)1e5+10
#define INF LLONG_MAX//无限大 
struct point{//储存每个点对 
	double x, y;
	point(){}
	point(double a, double b):x(a), y(b){}
	bool operator< (const point& b) const//按x为第一排序,y为第二排序,默认从小到大 
	{
		if (x != b.x) return x < b.x;
		else return y < b.y;
	}
}arr[N], temp[N];
bool cmp(const point& a, const point& b)
{
	return a.y < b.y;//按y值进行排序 
} 
double dis(const point& a, const point& b)//引用节省传参时间 
{
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));//计算两点距离 
}
double dfs(int l, int r)//每次分成两块 
{
	if (l == r) return INF;//如果分的块只有一个点,实际上我们是不希望计算的,就返回无限大
	if (l + 1 == r) return dis(arr[l], arr[r]);//分的是两个点,那就是递归出口,直接算距离 
	int mid = (l + r) >> 1;
	double m = min(dfs(l, mid), dfs(mid, r));
	int top = 0, i, j;
	for (i = l; i <= r; i++)//执行merge操作,因为有可能有两边的点距离是最小的 
	{
		if (fabs(arr[i].x - arr[mid].x) < m)//把有可能的点加入临时数组进行计算 
		{
			temp[top++] = arr[i];//这里可以二分优化,然而并没有用,因为有可能还是有O(n)的元素需要假如temp数组 
		}
	}
	sort(temp, temp+top, cmp);//按照y值排列,保证每个点要去比较的最多只有一定的点,不可能存在太多 
	for (i = 0; i < top; i++)//时间复杂度为是O(n)的,因为下面这个循环注定很快就会跳出,实际上是7n,已经证明出来最多是7个点 
	{
		for (j = i + 1; j  < top && temp[j].y - temp[i].y < m; j++)//当然也可以直接写成下标差小于等于7也行 
		{
			m = min(m, dis(temp[i], temp[j]));
		}
	}
	return m;//返回值 
}
int main(void)
{
	int n;
	scanf("%d", &n);
	int i;
	for (i = 1; i <= n; i++)
	{
		scanf("%lf%lf", &arr[i].x, &arr[i].y);
	}
	sort(arr + 1, arr + 1 + n);
	printf("%.3lf", dfs(1, n));
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值