分治法——平面最近点对问题

分治法——平面最近点对问题

**问题:**寻找一个二维平面上的最近点对的距离

思路:

等分平面,分治左右,合并左右问题和跨分割线的解。

首先考虑一维的情况,假如将所有的点在x轴的某个区间上从小到大排列,以中位数为基准点将该区间分为点数量基本等同的两部分p1,p2,那么最近点对有三种可能:p1中的最近点对,p2中的最近点对,跨p1p2的最近点对,分别对应了分治法中的子问题和子问题合并。对于子问题而言,最小的子问题是区间内仅有一个或者两个点,即最近点对的距离为无限或者是两点距离。对于跨p1p2的最近点对,假如采用遍历的方法,那么时间复杂度达到了O(n2),优化一下,其实只需要找到左边区间的最大点就可以了,找到后计算与其右边的那个点的距离。设p1p2中最近点对的距离中较小的那个距离为d,区间分界线为m,那么只需判断(m-d,m]中是否有点就可以了(至多有一个,否则与最近点对距离为d矛盾,遍历左边的子区间n/2个点即可),复杂度达到了O(n)。

考虑二维的情况,同样的思路,在确定分跨两个部分的点的最近距离时,每个点最多只有六个对面的可能满足条件的解(证明略,画图看看即可),遍历左边的子区间n/2个点,每个点再与6个点计算距离,一共6*n/2次判断,复杂度同样是O(n)。如何确定这个点所对应的六个点呢?可以按照y坐标排序,对于每个点比较其y坐标序下的前后六个点。

关于分割线临界处的问题,采用x主排,y辅排,利用x和y共同划分左右区间。

代码:

#include <bits/stdc++.h>

using namespace std;

const double INF = 1e20;

struct Point{
    double x;
    double y;
}p[105];

int mp[105];

double dis(Point p1,Point p2){
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

bool cmpy(const int &a,const int &b){
    return p[a].y < p[b].y;
}

bool cmpxy(const Point &a,const Point &b){
    if(a.x != b.x)
        return a.x < b.x;
    return a.y < b.y;
}

// 计算二维平面点集中的最近点对的距离
// x坐标区间为[p[l],p[r]]
// 输入:查找区间
// 输出:最近点对的距离
double nearestPair(int l,int r){
    // 处理最小子问题
    if(l==r){
        return INF;
    }else if(l+1 == r){
        return dis(p[l],p[r]);
    }else{
    // 处理一般问题
        // 划分子区间
        int mid = (l+r)/2;
        double d1 = nearestPair(l, mid);
        double d2 = nearestPair(mid+1, r);
        // 合并问题
        // 分离出宽度为2d的子区间
        double d = min(d1, d2);
        int k = 0;
        for(int i = l;i <= r;i++){
            if(fabs(p[i].x-p[mid].x)<=d)
                mp[k++] = i;
        }
        sort(mp, mp+k, cmpy);
        // 在符合条件的点中进行选择
        for(int i = 0;i < k;i++){
            for(int j = i-6>=0?i-6:0;j <= i+6<k-1?i+6:k-1;j++){
                if(i==j) continue;
                if(!((p[i].x<=p[mid].x)&&(p[i].y<=p[mid].y) && (p[j].x>p[mid].x)&&(p[j].y>p[mid].y))) break;
                double d3 = dis(p[i], p[j]);
                cout<<p[i].x<<" "<<p[j].x<<endl;
                if(d3<d) d = d3;
            }
        }
        return d;
    }
}

int main(){
    p[0].x = 1.1;
    p[1].x = 2.1;
    p[2].x = 3.14;
    p[3].x = 2.2;
    p[4].x = 1.11;
    p[5].x = 0.1;
    p[6].x = -2.2;
    p[7].x = -1.1;
    
    p[0].y = 2.2;
    p[1].y = 1.2;
    p[2].y = 2.2;
    p[3].y = -1.1;
    p[4].y = 0.22;
    p[5].y = 3.1;
    p[6].y = 2.1;
    p[7].y = 0.99;
    
    sort(p, p+8, cmpxy);
    cout<<nearestPair(0, 7);
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值