寻找最近点对

显然,寻找最近点对最原始的算法是计算所有的点对的距离来找出最近点对。这种算法依赖n的值,n的值越大运行时间越长。为了提高时效,可应用分治算法解决。

算法每次递归调用的输入为点的自集P和数组Y。P中的所有点按其Y坐标单调递增的顺序排列,其编号序列存入数组Y。X坐标同样排列存入。

1.划分

找出一条垂直平分线,将点集P划分为点数为P/2或者[P/2]和[P/2]+1的两个点集P1和P2。数组Y被划分为两个数组Y1和Y2,类似的X数组被划分成X1和X2。划分后的X1和Y1包含了P1中的点,并分别按X坐标和Y坐标单调递增的次序排序,X2和Y2同。

2.解决

把P划分为P1和P2后再进行两次递归调用。

第一次:找出P1中的最近点对,调用的输入为子集P1和数组Y1,返回最近点对的距离d1.

第二次:找出P2中的最近点对,调用的输入为子集P2和数组Y2,返回最近点对的距离d2.

置d=min(d1,d2)。

3.合并

注意可能还存在其距离小于d的点对。如果存在的话那么点对的一个点在P1中另一个在P2中,两个点必定相距垂直平分线d单位之内,即他们必定处于以垂直平分线为中心,宽度为2d的垂直带形区域内。更精确点来说,由于点对之间的垂直距离也少于d,所以是在d*2d的矩形区域内。

最近点对算法Nearest(S)流程如下:

double Nearest(S)

{

     n=|S|;

     if(n<2) return;

     1.m=S中各点x间坐标的中位数;

        构造S1和S2;

     2.d1=Nearest(S1);

         d2=Nearest(S2);

     3.dm=min(d1,d2);

     4.设P1是S1的中距垂直分割线l的距离在dm之内的所有点的组成的集合;

        P2是S2中距分割线l的距离在dm之内所有点组成的集合;

        将P1和P2中点依其Y坐标值排序;

        并设X和Y是相应的已排好序的点列;

     5.通过扫描X以及对于X中每个点检查Y中与其距离在dm之内的所有点(最多6个)可以完成合并;

          当X中的扫描指针逐次向上移动时,Y中的扫描指针可在宽为2dm的区间内移动;

          设dl是按这种扫描方式找到的点对间的最小距离;

     6.d=min(dm,dl);

     return d;

}

下面附上一份POJ3714 Raid的代码,类似与红蓝点对不过这个计算几何代码的时间是2000ms左右所以是不能通过UESTC 1170的1000ms的所以只能用贪心做

UESTC 1170博客题解链接:UESTC 1170

POJ计算几何代码如下:

#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
#define INF 1e100
typedef long long LL;
struct Node
{
  double x,y;
  bool flag;
}p[200005];
int Y[200005],n;
bool cmp(Node a,Node b)
{
  if(a.x==b.x) return a.y<b.y;
  return a.x<b.x;
}
bool cnp(int a,int b)
{
  return a<b;
}
double min_(double a,double b)
{
  if(a<b) return a;
  else return b;
}
double Dis(Node &a,Node &b)
{
  if(a.flag!=b.flag)
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
  else return INF;
}
double shortest_distance(int first,int last)
{
  if(last-first==1) return Dis(p[first],p[last]);
  else if(last-first==2) return min_(min_(Dis(p[first],p[first+1]),Dis(p[first],p[first+2])),Dis(p[first+1],p[first+2]));
  int mid=(first+last)/2;
  double dis=min_(shortest_distance(first,mid),shortest_distance(mid+1,last));
  if(dis==0)
    return 0;
  int y=0;
  for(int i=mid;p[mid].x-p[i].x<dis&&i>=first;i--)
  {
    Y[y++]=i;
  }
  for(int i=mid+1;p[i].x-p[mid+1].x<dis&&i<=last;i++)
  {
    Y[y++]=i;
  }
  sort(Y,Y+y,cnp);
  for(int i=0;i<y;i++)
  {
    for(int j=i+1;j<y&&p[Y[j]].y-p[Y[i]].y<dis;j++)
    {
      dis=min_(dis,Dis(p[Y[i]],p[Y[j]]));
    }
  }
  return dis;
}
int main()
{
  int t;
  scanf("%d",&t);
  while(t--){
  scanf("%d",&n);
  for(int i=0;i<n;i++)
  {
    scanf("%lf%lf",&p[i].x,&p[i].y);
    p[i].flag=false;
  }
  for(int i=n;i<2*n;i++)
  {
    scanf("%lf%lf",&p[i].x,&p[i].y);
    p[i].flag=true;
  }
  sort(p,p+2*n,cmp);
  printf("%.3f\n",shortest_distance(0,2*n-1));
  }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值