问题描述
设p1=(x1, y1), p2=(x2, y2), …, pn=(xn,yn)是平面上n个点构成的集合S,最近对问题就是找出集合S中距离最近的点对。
严格地讲,最接近点对可能多于一对,简单起见,只找出其中的一对作为问题的解。
求解思路
用分治法解决最近对问题,很自然的想法就是将集合S分成两个子集 S1和 S2,每个子集中有n/2个点。然后在每个子集中递归地求其最接近的点对,在求出每个子集的最接近点对后,在合并步中,如果集合 S 中最接近的两个点都在子集 S1或 S2中,则问题很容易解决,如果这两个点分别在 S1和 S2中,问题就比较复杂了。
一维的情况
此时,S中的点退化为x轴上的n个点x1, x2, …, xn。用x轴上的某个点m将S划分为两个集合S1和S2,并且S1和S2含有点的个数相同。递归地在S1和S2上求出最接近点对 (p1, p2) 和(q1, q2),如果集合S中的最接近点对都在子集S1或S2中,则d=min{(p1, p2), (q1, q2)}即为所求,如果集合S中的最接近点对分别在S1和S2中,则一定是(p3, q3),其中,p3是子集S1中的最大值,q3是子集S2中的最小值。
按这种分治策略求解最近对问题的算法效率取决于划分点m的选取,一个基本的要求是要遵循平衡子问题的原则。如果选取m=(max{S}+min{S})/2,则有可能因集合S中点分布的不均匀而造成子集S1和S2的不平衡,如果用S中各点坐标的中位数作为分割点,则会得到一个平衡的分割点m,使得子集S1和S2中有个数大致相同的点。
假设我们用x轴上的某个点m将S划分为两个集合S1和S2 ,使得S1={x∈S | x≤m} ;S2 ={ x∈S | x>m }。这样一来,对于所有p∈ S1和q∈S2 有p<q。递归地在S1和S2 上找出其最接近点对{p1,p2}和{q1,q2},并设 d=min{| p1- p2|,| q1- q2|},如果S的最接近点对是{p3,q3},即|p3-q3|<d,则p3和q3两者与m的距离不超过d,即| p3-m|<d,| q3 -m|<d。也就是说 ,p3∈(m-d,m],q3∈(m,m+d]。
由于每个长度为d的半闭区间至多包含S1中的一个点,并且m是S1和S2 的分割点,由图可以看出,如果(m-d,m]中有S中点 ,则此点就是S1中最大点。同理,如果(m,m+d]中有S中点,则此点就是S2 中最小点。因此,我们用线性时间就能找到区间(m-d,m]和(m,m+d]中所有点,即p3和q3。从而我们用线性时间就可以将S1的解和S2 的解合并成为S的解。
二维的情况
为了将平面上的点集S 分割为点的个数大致相同的两个子集S1和S2,选取垂直线x=m来作为分割线,其中,m为S中各点x坐标的中位数。由此将S分割为S1={p∈S | xp≤m}和S2={q∈S | xq>m}。递归地在S1和S2上求解最近对问题,分别得到S1中的最近距离d1和S2中的最近距离d2,令d=min(d1, d2),若S的最近对(p, q)之间的距离小于d,则p和q必分属于S1和S2,不妨设p∈S1,q∈S2,则p和q距直线x=m的距离均小于d,所以,可以将求解限制在以x=m为中心、宽度为2d的垂直带P1和P2中,垂直带之外的任何点对之间的距离都一定大于d。
对于点p∈P1,需要考察P2中的各个点和点p之间的距离是否小于d,显然,P2中这样点的y轴坐标一定位于区间[y-d, y+d]之间,而且,这样的点不会超过6个。
应用分治法求解含有n个点的最近对问题,其时间复杂性可由下面的递推式表示:
合并子问题的解的时间f(n)=O(n),T(n)=O(nlog2n),蛮力法求解最近点对问题,时间复杂度为O(n2)。
代码实现
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#include<conio.h>
#include<graphics.h>
#define MIN(a,b) ((a.dis)>(b.dis)?(b):(a))
#define fabs(a) ((a)<(0)?(-(a)):(a))
//用于记录一维的最近点对
typedef struct{
double a, b;
double dis;
}record;
//二维的点
struct point
{
int x, y;
};
//求二维的距离函数
double Distance(point a, point b){
return sqrt((double)((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y)));
}
//一维的比较排序函数
/*int cmp(const void *x, const void *y){
double a, b;
a = *((double *)x);
b = *((double *)y);
return a > b;
}*/
//划分
int Partition(point P[], int first, int end){
//初始化待划分区间
int i = first, j = end;
while (i<j)
{
//右侧扫描
while (i<j&&P[i].y <= P[j].y)
{
j--;
}
//将较小的记录换到前面
if (i<j)
{
int temp = P[i].y;
P[i].y = P[j].y;
P[j].y = temp;
i++;
}
while (i<j&&P[i].y <= P[j].y)
{
i++;
}
if (i<j)
{
int temp = P[i].y;
P[i].y = P[j].y;
P[j].y = temp;
j--;
}
}