平面最近点对学习笔记
平面上n个点构成了点集S,求集合S中的最近点对。
求最近点对有两种方法:
1.蛮力法(适用于n较小的情况下)
2.分治法
下面就来详细介绍一下这两种方法QWQ
一、蛮力法
1.算法描述
已知集合S中有n个点,一共可以组成n(n-1)/2对点对,蛮力法就是对这n(n-1)/2对点对逐对进行距离计算,通过循环求得点集中的最近点对
这种方法是真的名副其实超级暴力了,就是直接循环嵌套把n个点两两匹配找出最近点对
2.核心代码
double minn=123456;//minn用于存储最近点对之间的距离 int s1,s2;//存储最近点对的两个点编号 for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++){ double dis=D(i,j);//D(i,j)表示点i和点j之间的距离 if(dis<minn){//如果此两点间的距离小于当前最小距离就更新信息 minn=dis; s1=i; s2=j; } }3.时间复杂度
此算法一共要执行n(n-1)/2次,所以时间复杂度为O(n2)
(不会算时间的孩纸伤不起啊QAQ)
这种暴力的方法应该用得不是很多……?还是主要要掌握下面这种分治法呀^_^
二、分治法
1.算法描述
分治法的思想就是将S进行拆分,分为2部分求最近点对。算法每次选择一条垂线L,将S拆分左右两部分为SL和SR,L一般取点集S中所有点的中间点的x坐标来划分,这样可以保证SL和SR中的点数目各为n/2
这个方法可以用递归实现,对于当前状态下的垂线L,最近点对可能会有三种情况
<1>最近点对出现在L的左侧
<2>最近点对出现在L的右侧
<3>最近点对跨过L,即最近点对一个点在L左侧,一个点在L右侧
于是我们要先算出L左侧的最近点对和L右侧的最近点对,取这两个最近点对之间的距离较小值记为d,再从L向左右分别扩展长为d的距离,枚举在这一段范围内的点两两匹配,找到跨过L的最近点对再和d比较,这样就可以得到所有n个点中的最近点对了
2.核心代码 (放一个模板的链接)
在我锲而不舍地修改了n次之后终于AC的代码1 #include<bits/stdc++.h> 2 #define go(i,a,b) for(register int i=a;i<=b;i++) 3 using namespace std; 4 double INF=2<<20; 5 const int N=200002; 6 struct point{ 7 double x,y; 8 }p[N]; 9 int n; 10 int middle[N]; 11 double D(int a,int b){//用于计算两点之间的距离 12 double X=p[a].x-p[b].x; 13 double Y=p[a].y-p[b].y; 14 return sqrt(X*X+Y*Y); 15 } 16 bool cmp1(const point &A,const point &B){//预处理 17 //以横坐标为第一关键字,纵坐标为第二关键字排序 18 if(A.x==B.x) 19 return A.y<B.y; 20 return A.x<B.x; 21 } 22 bool cmp2(const int &A,const int &B){ 23 return p[A].y<p[B].y; 24 } 25 double work(int l,int r){ 26 double d=INF; 27 if(l>=r) return d;//如果只有一个点的话距离就是无穷大啦 28 if(l+1==r){//两个点的话最短距离就是这两点之间的距离 29 return D(l,r); 30 } 31 int mid=(l+r)/2; 32 double dl=work(l,mid);//dl记录左半部分的最短距离 33 double dr=work(mid+1,r);//dr记录右半部分的最短距离 34 d=min(dl,dr);//取较小值 35 int num=0; 36 go(i,l,r)//找到中间扩展出的区域内的点 37 if(abs(p[mid].x-p[i].x)<=d)//注意这里要取绝对值abs()函数 38 middle[++num]=i;//记录 39 sort(middle+1,middle+1+num,cmp2);//再次排序 40 //这次排序以纵坐标为关键字,为后面的优化做准备 41 go(i,1,num-1)//枚举记录好的点 42 for(int j=i+1;j<=num&&p[middle[j]].y-p[middle[i]].y<d;j++){ 43 //因为是按纵坐标升序排列的,所以如果两个点的纵坐标之差已经大于当前最短 44 //距离就可以跳过啦 45 double dm=D(middle[i],middle[j]);//dm记录中间区域的最短距离 46 d=min(d,dm); 47 } 48 return d; 49 } 50 int main(){ 51 scanf("%d",&n); 52 for(int i=1;i<=n;i++) 53 scanf("%lf%lf",&p[i].x,&p[i].y); 54 sort(p+1,p+1+n,cmp1); 55 printf("%.4lf\n",work(1,n)); 56 return 0; 57 } 58 /* 59 DTT小朋友说归并排序会比快排要优秀一些啊……不过我没有尝试诶,主要是 60 快排也很优秀啦,而且快排打起来比归并要方便啊(对我就是懒) 61 */3.时间复杂度
总的时间复杂度为O(3nlogn)
(别问我怎么算的我是真的不会算TAT)