平面最近点对学习笔记

平面上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.核心代码  (放一个模板的链接)

 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 */
在我锲而不舍地修改了n次之后终于AC的代码

3.时间复杂度

总的时间复杂度为O(3nlogn)

(别问我怎么算的我是真的不会算TAT)

posted @ 2019-02-10 16:29  小叽居biubiu  阅读(368)  评论(0编辑  收藏  举报