问题描述:给定平面上n个点,每个点坐标分别为(x,y),求其中的一对点,使得在n个点组成的所有点对中,该点对的距离最小。
最简单的做法是找到所有点对的距离,然后求最小值,但这样做效率太低,时间复杂度为O(n*n), 求最小值的问题,我们很容易联想到分治法,但是分治法一般是用在一维的条件下,那么问题来了,当扩展到二维的情况下,如何使用分治法呢?
一般想到分治法,我们一般最开始可能就会想到,把n个点分成左右两部分, 为了将平面上的点集S分割为大小大致相同的2个子集S1和S2, 我们可以首先把所有点根据横坐标x从小到大排序,我们选取x=mid来作为分割线。其中,mid为S中所有点的横坐标的中位数。 这样可以将点集合S分为2部分,S1={P属于S| x§<=m} 和 S2={P属于S| x§>m}, 这样S1和S2中坐标点的个数大致相等。
然后分别求左、右两部分里面点的最短距离(设其最短距离分别为left_min和right_min),现在设d=min(left_min,right_min),若S中存在最近点对(p,q)之间的距离小于d,那么p和q必分属于S1和S2,p,q的横坐标距直线x=mid的距离也均小于d.
在一维的情况下,距分割点距离为d的2个区间(mid-d,mid)和(mid,mid+d)中最多各有一个点(若多余一个点,与左右区间最小距离为d矛盾)。在二维情况下则要稍微复杂些, 最坏情况下可能左右各有n/2个点, 那么如果我们遍历这n/2个点对,需要的时间为n*n/4, 这样做时间效率太低,显然我们不能这样做。考虑到对P1中任意一点p,若他与p2中的某个点q构成一个最短距离,设p的坐标为(x,y),那么显然q的横坐标的范围为(mid,mid+d),纵坐标范围为(y-d,y+d).
那么对于每一个点p, 与其构成最短距离的点必然在一个d2d的矩形框内(该矩形框内可能有多个点,但是任意2个点之间的距离都不超过d,接下来我们证明该矩形框中的点最多不超过一个常数),我们把该矩形框划分为6个(d/2)(2d/3)的矩形, 由于该矩形的对角线长度为
(
d
/
2
)
2
+
(
2
d
/
3
)
2
=
5
d
/
6
\sqrt{(d/2)^{2}+(2d/3)^{2}}=5d/6
(d/2)2+(2d/3)2=5d/6 小于d, 故每个矩形框内实际上最多只可能有一个这样的点, 这样对于任意一个点p,与其对应的可能构成最小距离的点最多不超过6个,这样左右之间的匹配次数最多是6n次。
我们可以把距离x=mid左右距离不超过d的所有的点按纵坐标排序,这样在找这些点时会方便一些。
这样时间复杂度T(n)=2T(n/2)+O(n), T(n)=O(n*logn)
//求平面中点之间的最短距离
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct point
{
double x;
double y;
};
bool sortBy_x(point& point1, point& point2) //根据x坐标从小到大排序
{
if (point1.x == point2.x)
return point1.y < point2.y;
return point1.x < point2.x;
}
bool sortBy_y(point& point1, point& point2) //根据纵坐标从小到大排序
{
return point1.y < point2.y;
}
double getDistance(point& point1, point& point2)
{
double dis_x = point1.x - point2.x;
double dis_y = point1.y - point2.y;
return sqrt(dis_x*dis_x+dis_y*dis_y);
}
double getMin(vector<point>& vec, int low, int high)
{
if (high - low == 1) //2个结点
{
return getDistance(vec[low], vec[low + 1]);
}
else if (high - low == 2) //3个结点
{
double dist1= getDistance(vec[low], vec[low + 1]);
double dist2 = getDistance(vec[low], vec[low + 2]);
double dist3 = getDistance(vec[low+1], vec[low + 2]);
return min(min(dist1, dist2), dist3);
}
else
{
int mid = (low + high) / 2;
double left_min = getMin(vec, low, mid);
double right_min = getMin(vec, mid + 1, high);
double d = min(left_min, right_min);
vector<point> res;
res.erase(res.begin(), res.end());
int i, j,k=0;
for (i = low; i <= high; i++) //遍历一遍数组,得到与横坐标与 中点横坐标距离在d以内的点
{
if (fabs(vec[i].x - vec[mid].x) < d)
res.push_back(vec[i]);
}
sort(res.begin(),res.end(),sortBy_y); //根据纵坐标从小到大排序
for (i = 0; i < res.size() - 1; i++)
{
for (j = i + 1; j < res.size(); j++) //求距离的点 为与i纵坐标的距离在d以内
{
if (res[j].y - res[i].y >= d)
break;
double dp = getDistance(res[i], res[j]);
if (dp < d)
{
d = dp;
}
}
}
return d;
}
}
int main()
{
int i, j, n;
cin >> n; //输入点的个数
vector<point> vec;
for (i = 0; i < n; i++)
{
point point1;
cin >> point1.x >> point1.y;
vec.push_back(point1);
}
sort(vec.begin(), vec.end(), sortBy_x);
cout << "所有点之间的最短距离为:" << getMin(vec, 0, vec.size() - 1) << endl;
system("pause");
return 0;
}