寻找最近点对-分治法

1. 题意

n>= 2的点的集合中寻找最近点对,最近点对是指通常意义下的欧几里得距离:即点p1(x1, y1), p2(x2, y2)之间的距离为sqrt((x1 - x2)^2 + (y1- y2)^2),Q中有2点重合时,最近点对的距离是0

最简单的算法是暴力搜索,查看所有的O(n^2)个点对。现在利用分治法解决,其运行时间是T(n) = 2 T(n/2) + O(n),该算法的时间复杂度为O(nlgn);

2.  分治法

对于每次递归调用的输入为子集P,P中的所有点按其X坐标的单调递增的顺序排序,

1)  当输入子集P的递归调用首先检查是否P中点个数是否小于3,如果小于3,则按暴力搜索的方案解决;

2)  当输入子集p中的点的个数大于3时,则递归调用下面的分支方法:

分解过程:

找出一条垂线l, 他把输入点集P划分为满足一下条件的两个集合PL和PR:|PL|=[|P| / 2], |PR|=[|P| / 2], PL中的所有点在线l上或者l的左侧,PR中所有点在线l上或者l的右侧;子集P中所有点按X坐标单调递增的顺序进行排序。

解决过程:

把P划分为PL和PR后,在进行2次递归调用,一次找出PL中的最近点对,另一次找出PR中的最近点对,第一次调用的输入为子集PL,第二次调用的输入为子集PR。对对PL和PR,返回最近点对的距离分别是minL和minR,则置minD =min(minL, minR);

合并过程:

最近点对要么是某次递归调用找出的距离为minD的点对,要么是PL中的一个点和PR中的一个点组成的点对,所以我们要确定的是是否存在其距离小于minD的一个点对。若存在这样的点对,则点对中的两个点必定在距离垂直线l的minD距离范围内。因此他们都处在以垂线l为中心宽度为2*minD的垂直带型区域中,如图1所示。

为了找出这样的点对算法需要做这样的工作:

1) 把数组中PL或者PR中所有水平距离不在宽度为2* minD的带型区域中的点去掉后得到点集P1;

2) 再把点集p1中所有点对的垂直距离不在minD之内的点去掉后得到点集P2;

3) 在点集P2中计算每个点对得到欧几里的距离minX,然后minX跟minD比较取较小值,返回较小值

                                   图 1

证明最近点对需要检查子集P中至多有8个点可能位于minD* 2minD的一些关键概念:

1) 如果pl属于PL,pr属于PR,并且pl和pr之间距离小于minD,那么他们必定位于以直线l为中心线的minD * 2minD的矩形区域内。

2) 4个两两之间的距离至少为minD的必定位于同一个minD * minD正方形内。

 

                                              2

考察图2矩形左半边的minD *mind的正方形,因为PL所有点之间的距离至少为minD距离,所以至多有4个点位于该正方形内即正方形的四个顶点,显然正方形内部的点不能满足PL中所有点距离至少为minD;类似的PR中至多有4个点可能位于该矩形右半边的minD* minD正方形内。直线l上至多含有4个点,若有两队重合点,没对包含一个PL中点和一个PR中的点,一对就是直线l和矩形上面一条边的交汇处,另一对就是直线l和矩形下面一条边的交汇处。因此点集P中至多含有8个点肯能位于矩形内。

 

示例代码:

#include <iostream>

#include <algorithm>

#include <math.h>

using namespace std;

 

#define NSIZ 1100000

 

typedef struct Node_

{

       int x, y;

}Node;

 

Node nodes[NSIZ];

 

bool cmp(Node a, Node b)

{

       return a.x < b.x;

}

 

double Distance(Node a, Node b)

{

       double dis = (a.x - b.x) * ( a.x - b.x) + (a.y - b.y)* (a.y - b.y) * 1.0;

       return sqrt(dis);

}

 

double mergePoint(intleft,int right)

{

       double resultLeft = 0, resultRight = 0, minLen =INT_MAX;

       int i = 0, j = 0;

       double tmp = 0;

 

       if (right > left + 4)

       {

              int mid = left + ((right - left) >> 1);

              resultLeft= mergePoint(left, mid);

              resultRight= mergePoint(mid, right);

              minLen= min(resultLeft, resultRight);

 

              for (i = mid; i >= left && nodes[i].x >nodes[mid].x - minLen; --i)

              {

                     for (j = mid; j <= right && nodes[j].x< nodes[mid].x + minLen; ++j )

                     {

                           if (abs(nodes[j].y - nodes[j].y) < minLen)

                           {

                                  tmp= Distance(nodes[i], nodes[j]);

                                  if (tmp < minLen)

                                  {

                                         minLen= tmp;

                                  }

                           }

                     }

              }

       }

       else

       {

              for (i = left; i < right; ++i)

              {

                     for (j = left + 1; j <= right; ++j)

                     {

                           if (i != j )

                           {

                                  tmp= Distance(nodes[i], nodes[j]);

                                  if (tmp < minLen)

                                  {

                                         minLen= tmp;

                                  }

                           }

                     }

              }

       }

 

       return minLen;

}

 

 

int main()

{

       int t, n, i, mid;

       double res;

       scanf("%d", &t);

       while(t--)

       {

              res= 0;

              scanf("%d", &n);

 

              for (i = 0;i < n; ++i)

              {

                     scanf("%d %d", &nodes[i].x,&nodes[i].y);

              }

 

              sort(nodes,nodes + n, cmp);

 

              res= mergePoint(0, n - 1);

              printf("%.3lf\n", res);

 

       }

 

       return 0;

}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值