1.问题
给定平面上n个点,找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的。
2.解析
分治法的一般步骤
Step1:Devide——将要解决的问题划分成若干规模较小的同类问题
Step2:Conquer——当子问题划分得足够小时,用较简单的方法解决 (递归)
Step3:Combine——将子问题的解逐层合并构成原问题的解
以中间点为划分线的最近点对的距离有三种情况,一种是都在左边点集存在最短距离,一种是都在右边点集存在最短距离,最后一种则是一个点在左边点集一个点在右边点集的情况。
我们将平面上的点根据横坐标为主纵坐标为辅的原则进行排序,假设第一种和第二种情况都已经存在d1和d2为最短路径,只需要判断第三种情况即可。一种是暴力枚举法,但是由于复杂度高,所以不予考虑,另一种则是利用已知的左右两边最短距离d=min(d1,d2),以中间点为中心划分左右距离为d的平行区域,再根据纵坐标排序结果,检查纵坐标之差小于d的情况,再判断d的取值即可。
例如,根据下图所示
我们以G点为中心,划分左右两个点集,由于递归实现,就采取已知左右两边的最短距离,考虑第三种情况的表示。
可以看到的是,在d为1时,只需要在G左右建立距离分别为1的直线,只要超过这两条直线的显然与G的距离超过d,不予考虑,同时也可以以此判断上下距离,所以就从纵坐标排序开始检查即可。
3.设计
#include <iostream>
#include <algorithm>
#include <map>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct{
double x;
double y;
}Point;
double INF =1e20;
//排序规则:先按照横坐标排序,横坐标相同就按照纵坐标排序。
bool cmp(const Point& p1,const Point& p2){
return p1.x==p2.x?p1.y<p2.y:p1.x<p2.x;
}
//内联函数获取两点间距离
inline double dis(const Point& p1,const Point& p2){
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
//最近点对算法,输出left点到right点之间最近两点对的距离
double divide(int left,int right,Point* p){
Point point[100];
if(left==right) return INF;
//分治
int m=(left+right)/2;
Point p1;
p1=p[m];
//求解左边的点集最短距离和右边的最短距离并取最小
double d=min(divide(1,m,p),divide(m+1,right,p));
int i=1,j=m+1,k=0;
//将左右两边的点根据横坐标再纵坐标排序的规则合并收入临时点数组中
while(i<=m&&j<=right){
if(p[i].y<p[j].y) point[k++]=p[i++];
else point[k++]=p[j++];
}
while(i<=m) point[k++]=p[i++];
while(j<=right) point[k++]=p[j++];
//讨论一点在左半部分一点在右半部分的情况
//考虑纵坐标差值在d范围内的点,判断最短距离
k=0;
for(i=1;i<=right;i++){
if(fabs(p1.x-point[i-1].x)<d) point[k++]=point[i-1];
}
for(i=0;i<k;i++){
for(j=i+1;point[j].y-point[i].y<d&&j<k;j++){
d=min(d,dis(point[i],point[j]));
}
}
return d;
}
int main(){
int n,i;
cin>>n;
Point p[100];
for(i=0;i<n;i++){
cin>>p[i].x>>p[i].y;
}
sort(p,p+n,cmp);
cout<<divide(0,n-1,p);
}
// input:
// 11
// -4 -1
// -3 -1
// -3 1
// -2 0
// 0 0
// -0.2 1.4
// 0.4 1.4
// 1 -1
// 2 1
// 2 2
// 3 0
// output:
// 0.6
4.分析
根据横纵坐标的排序复杂度为O(nlogn)
每次递归划分区间的深度不会超过O(logn)
因为只涉及合并两个有序区间,每一层的归并复杂度为O(n)
总复杂度就是O(nlogn)
5.源码
https://github.com/Chenzh0205/Algorithm/tree/main/%E4%BD%9C%E4%B8%9A5