题意即为平面上有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));
}