平面最近点对算法
见oj/1299.cpp和oj/1299_multiset.cpp.
问题
给定平面上n(n≥2)个点,求欧几里得距离最近的两个点。
方法一:暴力
枚举所有点对,O(n2)。
代码
方法二:分治
一个很优美的思路。
先将所有点按照x坐标排序。假设当前我们想要处理下标[l,r)内的点,我们把这个区间分成两部分:m=⌊2l+r⌋,分别递归找到[l,m)和[m,r)内的最近点对距离d1和d2,令当前最短距离d=min(d1,d2)。
现在只需要考虑跨越左右两边的点。假设下标为i的点坐标是(xi,yi)。首先观察到以下两点:
- xi∈(xm−d,xm+d)的点才是有意义的,否则一定不会对答案有贡献(离中线太远了);
- 每个满足前一个条件的点i,只需要考虑(xm−d,xm+d)×(yi−d,yi]内的点,其他要么不会产生贡献,要么会被其他的点计算过。
令区域Bi=(xm−d,xm+d)×(yi−d,yi],我们现在证明Bi中除了i本身,最多还有7个点:
- 把Bi划分成2d×2d的小块区域,这样会得到8个子区域。
- 每个小区域内,最多只有一个点,因为两个点的距离不会超过2d,而我们知道每个区域内的点都属于左边或都属于右边,两点之间距离是不会小于d的。
- i号点在且仅在一个小区域内,其他7个区域最多每个区域一个点,所以最多7个点。
也就是说,每个点只需要遍历最多7个候选点。总复杂度为:
- 按x排序,O(nlogn);
- 递归函数:T(n)=2T(n/2)+O(nlogn)+O(n),其中O(nlogn)是按照y排序,O(n)是遍历中线旁边的点。这样T(n)=O(nlog2n)
- 所以总复杂度为O(nlog2n)。
但其实还可以进一步优化:递归处理完左右区间后,我们已经不需要x升序,所以我们不妨让每次函数执行完,就把当前区间按照y从小到大排序,那么左右区间都这么排好后,只需要一个O(n)的归并就好了。这样总复杂度就是O(nlogn)。
代码
方法三:暴力?
观察方法二,发现7个点条件并不需要对半分,只要分成两块一定有这个条件。
还是先按照x从小到大排序,假设我们现在已经处理好前i−1个点的最小距离d,现在考虑加入第i个点。那么可能对i产生贡献的点一定在(xi−d,xi]×(yi−d,yi+d)区域内,这个区域内除了i也最多是7个点。
所以问题转化为如何快速找到这7个点。考虑维护一个multiset,按照y排序,每次遍历到新的i,就把横坐标xi−xj≥d的节点都删掉,剩下只需要考虑y坐标在区域内,使用multiset::iterator遍历i周围的点就好了。
时间复杂度还是O(nlogn),省去了递归,可能还会快一点?
代码