分治法之最近对问题
一、问题描述
设p1=(x1, y1), p2=(x2, y2), …, pn=(xn, yn)是平面上n 个点构成的集合S,最近对问题就是找出集合S中距离最近的点对。
严格地讲,最近的点对可能多于一对,简单起见,只找出其中的一对作为问题的解。
二、最近对的分治策略
2.1 一维情况
2.1.1解法
为了使问题易于理解,先考虑一维的情形。此时,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中的最小值。
2.1.2 m点的选取问题
按这种分治策略求解最近对问题的算法效率取决于划分点m的选取,一个基本的要求是要遵循平衡子问题的原则。如果选取m=(max{S}+min{S})/2,则 有可能因集合S中点分布的不均匀而造成子集S1和S2 的不平衡,如果用S中各点坐标的中位数(即S的中值)作为分割点,则会得到一个平衡的分割点m, 使得子集S1和S2中有个数大致相同的点。在代码中的实现方法是先将集合S中的点的x坐标做一个升序排列,再设定low=0,high=s.length-1;计算low和high的中间值mid.作为中间的分割线。
2.2 二维情况
2.2.1 最近对的三种区间
接下来考虑二维的情形, 此时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,(pq<d,pm,mq肯定都小于d.)所以,可以将求解限制在以x=m为中 心、宽度为2d的垂直带P1和P2中,垂直带之外的任何点对之间的距离都一定大于d。
2.2.2 对区间S2的选点规则
对于点p∈P1,需要考察P2中的各个点和点p之间的距离是否小于d,显然,P2中这样点的y轴坐标一定位于区间[y-d, y+d] 之间 ,(以p为圆心画一个直径为d的圆,最近的点在里面。所以y的上下范围是y-d,y+d,这里的y是p的y值)。而且,这样的点不会超过6个(因P2中点与点之间的距离不能小于d,有相关几何定理已经证明)。故可将P1和 P2中的点按照y轴坐标值升序排列,顺序处理P1中的点p,同时在P2中选出符合条件的候选点(最多6个),计算它们与点 p之间的距离。
情况a:
正常情况下点少于6个,此时依次边筛选符合的点(y值符合)边计算两点间的距离。在事先筛选出x值在m-d,m+d范围内的点后再筛选y值在y-d~y+d的范围的点,边筛选边计算距离,例如考察A点的y值是否在范围内,在范围内以后就计算PA的距离,再将这个距离和之前算出的最小距离比较,比最小的小就赋值给最小的,否则就检查下一个点。直到所有的点都检查完毕。此时也得出了整个区间的最小距离.
情况 b:最坏情况是点均在边上,根据组合数学鸽笼原理,可以知道符合条件的点最多不超过6个点。而且如果是6个点,这6个点一定在边上,因为要求每个点间的距离大于d
如下图中的点数大于6个的情况不会出现。
因为GF距离比d小,所以不可能出现在范围内。 而且此时该区间中位数的数组下标值也不再是x=m,需要重新调整。
让我们来先看看代码
2.2.3 代码分析
点的存储结构
/*
点的存储结构
*/
struct point
{
int x, y;
};
随机生成点
/*
随机生成点
*/
void Initial(int N, point* p)
{ srand(time(0)); //种子为数据的规模
int i;
for (i = 0; i < N; i++) {
//产生随机数,X集和Y集中点的内容是一样的,只是排序的方法不同而已
p[i].x = rand()%(10-1)+1;
p [i].y = rand()%(10-1)+1;
}
}
求两点之间距离
/*
求两点之间距离
*/
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)));
}
求最近对函数
double Closest_DC(point S[ ], int low, int high)/*由于本身会迭代,所以无论是传递结构体指针还是在内部直接打印,最后都不能正确打印出最近点对的坐标值,在这里就不加这个东西了。*/
{
QuickSortx(S, 0, high);//表示将集合S中的点的x值按照从0到high值进行升序排序。
double d1,d2,d3,d;
int mid,i,j,index;
point P[100];//用于存放S1,S2
if (high-low==1)//如果数组区间长度为1,说明只有两个点,则直接返回。
{
return Distance(S[low], S[high]);
}
if(high-low==2){//如果有三个点ABC,分别算AB BC AC距离比较,然后选出最小的值返回
d1=Distance(S[low], S[low+1]);
d2=Distance(S[low+1], S[high]);
d3=Distance(S[low], S[high]);
if((d1<d2)&&(d1<d3))
return d1;
else if (d2<d3)
return d2;
else
return d3;
}
mid=(low+high)/2;//如果是4个点及以上
d1