文章目录
- 1. 问题描述
- 2. 解题思路
- 3. 实现代码
- 4. POJ 3714
1. 问题描述
二维平面上有n个点,如何快速计算出两个距离最近的点对?
2. 解题思路
- 暴力做法是,每个点与其他点去计算距离,取最小的出来,复杂度O(n2)
- 采用分治算法
- 将数据点按照 x 坐标排序,找到中位点,过中位点划线 x = mid_x 将数据分成2部分,递归划分,直到两个半边只有1个或者2个数据,只有1个数据点,最短距离返回无穷大,有2个点直接返回2点的距离
- 合并左右两边的结果,取左右两边的最短距离的较小值为 d = min{dl,dr},那么在 x = mid_x 的 ± d 范围内的左右点对才有可能距离比 d 更小(好理解)
- 对这个范围内的点,再按照 y 坐标排序,查找两个点的 y 差值小于 d 的点对(重点在这里,见下面分析),计算其距离是否比 d 更小
假如在这个范围内的有1,2,3,4,5,6六个点(按 y 坐标排序),寻找距离小于 d 的点对,如果暴力查找,复杂度还是 n2,我们可以看出点4只有可能在其上下y坐标 ± d 的范围内找到满足距离小于 d 的点匹配,点1和点4不可能距离小于 d,左边的点最多可以有4个右边的点使得其距离小于 d
所以,步骤3是O(n)复杂度。
T(1) = 1;T(n) = 2*T(n/2)+n;高中数学即可求得T(n)是O(nlogn)的复杂度。
3. 实现代码
/*** @description: 2维平面寻找距离最近的点对(分治)* @author: michael ming* @date: 2019/7/4 23:16* @modified by: */
#include <iostream>
#include <cmath>
#include <vector>
#include <ctime>
#include <algorithm>
#define LEFT_BOUND 0.0
#define RIGHT_BOUND 100.0
#define LOWER_BOUND 0.0
#define UPPER_BOUND 100.0 //随机点的范围
using namespace std;
class Point//点
{
public:int id;double x, y;Point(int index, double x0, double y0):id(index),x(x0),y(y0){}
};
typedef vector<Point> PointVec;
bool compx(const Point &a, const Point &b)
{if(a.x != b.x)return a.x < b.x;return a.y < b.y;
}
bool compy(const Point &a, const Point &b)
{return a.y < b.y;
}class ClosestPoint
{PointVec points_vec;int numOfPoint;
public:ClosestPoint(){srand(unsigned(time(0)));double x, y;cout << "请输入测试点个数,将随机生成散点:";cin >> numOfPoint;for(int i = 0; i < numOfPoint; ++i){x = LEFT_BOUND + (double)rand()/RAND_MAX *(RIGHT_BOUND-LEFT_BOUND);y = LOWER_BOUND + (double)rand()/RAND_MAX *(UPPER_BOUND-LOWER_BOUND);cout << i+1 << " (" << x << "," << y << ")" << endl;points_vec.emplace_back(i+1,x,y);//生成点的动态数组}}double dist(const Point &a, const Point &b){double dx = a.x - b.x;double dy = a.y - b.y;return sqrt(dx*dx + dy*dy);//返回两点之间的距离}void bfCalDist()//暴力求解{size_t num = points_vec.size();if(num <= 1){cout << "输入个数<=1 !!!" << endl;return;}int i, j, s, t;double distance = RAND_MAX, d;for(i = 0; i < num; ++i)for(j = i+1; j < num; ++j){d = dist(points_vec[i], points_vec[j]);if(d < distance){distance = d;s = points_vec[i].id;t = points_vec[j].id;}}cout << "点" << s << "到点" << t << "的距离最小:" << distance << endl;}double calcDist(size_t left, size_t right, size_t &s, size_t &t){if(left == right)//一个点,返回无穷大return RAND_MAX;if(left+1 == right)//两个点,直接计算距离{s = points_vec[left].id;t = points_vec[right].id;return dist(points_vec[left],points_vec[right]);}sort(points_vec.begin()+left,points_vec.begin()+right+1,compx);//把点群按照x排序size_t mid = (left+right)/2;double mid_x = points_vec[mid].x;double distance = RAND_MAX, d;distance = min(distance,calcDist(left,mid,s,t));//递归划分左边distance = min(distance,calcDist(mid+1,right,s,t));//递归划分右边size_t i, j, k = 0;PointVec temp;//存储临时点(在mid_x左右d范围内的)for(i = left; i <= right; ++i){if(fabs(points_vec[i].x-mid_x) <= distance){temp.emplace_back(points_vec[i].id,points_vec[i].x,points_vec[i].y);k++;}}sort(temp.begin(),temp.end(),compy);//再把范围内的点,按y排序for(i = 0; i < k; ++i){for(j = i+1; j < k && temp[j].y-temp[i].y < distance; ++j){//在临时点里寻找距离更小的,内层循环最多执行不超过4次就会退出d = dist(temp[i],temp[j]);if(d < distance){distance = d;s = temp[i].id;t = temp[j].id;}}}return distance;}void closestDist()//调用分治求解{size_t num = points_vec.size();if(num <= 1){cout << "输入个数<=1 !!!" << endl;return;}size_t s, t; s = t = 0;//记录起终点double d = calcDist(0,num-1,s,t);cout << "点" << s << "到点" << t << "的距离最小:" << d << endl;}};
int main()
{ClosestPoint cp;clock_t start, end;cout << "方法1,暴力求解:" << endl;start = clock();cp.bfCalDist();end = clock();cout << "耗时:" << (double)(end - start) << "ms." << endl;cout << "-------------------" << endl;cout << "方法2,分治求解:" << endl;start = clock();cp.closestDist();end = clock();cout << "耗时:" << (double)(end - start) << "ms." << endl;return 0;
}
4. POJ 3714
http://poj.org/problem?id=3714
相同的问题,只是数据位置分为2类(人,核电站),计算距离时,需判断是不同的类,否则返回一个很大的数。
以下代码显示Wrong Answer,谁帮忙看下。测试样例输出是一样的。
/*** @description: poj3714求解最近的核电站距离* @author: michael ming* @date: 2019/7/6 0:09* @modified by: */
#include<iomanip>
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#define DBL_MAX 1.7976931348623158e+308
using namespace std;
class Point//点
{
public:int id;int x, y;Point(int index, int x0, int y0):id(index),x(x0),y(y0){}
};
typedef vector<Point> PointVec;
bool compx(const Point &a, const Point &b)
{if(a.x != b.x)return a.x < b.x;return a.y < b.y;
}
bool compy(const Point &a, const Point &b)
{return a.y < b.y;
}class ClosestPoint
{PointVec points_vec;int numOfPoint;
public:ClosestPoint(){int x, y;cin >> numOfPoint;for(int i = 0; i < numOfPoint; ++i){cin >> x >> y;points_vec.push_back(Point(0,x,y));//生成站点的动态数组(0表示站)}for(int i = 0; i < numOfPoint; ++i){cin >> x >> y;points_vec.push_back(Point(1,x,y));//加入人的位置(1表示人)}}double dist(const Point &a, const Point &b){if(a.id == b.id)return DBL_MAX;//相同的类型,返回很大的数int dx = a.x - b.x;int dy = a.y - b.y;return sqrt(double(dx*dx + dy*dy));//返回两点之间的距离}double calcDist(size_t left, size_t right){if(left == right)//一个点,返回无穷大return DBL_MAX;if(left+1 == right)//两个点,直接计算距离{return dist(points_vec[left],points_vec[right]);}sort(points_vec.begin()+left,points_vec.begin()+right+1,compx);//把点群按照x排序size_t mid = (left+right)/2;double mid_x = points_vec[mid].x;double distance = DBL_MAX, d;distance = min(distance,calcDist(left,mid));//递归划分左边distance = min(distance,calcDist(mid+1,right));//递归划分右边size_t i, j, k = 0;PointVec temp;//存储临时点(在mid_x左右d范围内的)for(i = left; i <= right; ++i){if(fabs(points_vec[i].x-mid_x) <= distance){temp.push_back(Point(points_vec[i].id,points_vec[i].x,points_vec[i].y));k++;}}sort(temp.begin(),temp.end(),compy);//再把范围内的点,按y排序for(i = 0; i < k; ++i){for(j = i+1; j < k && temp[j].y-temp[i].y <= distance; ++j){//在临时点里寻找距离更小的,内层循环最多执行不超过4次就会退出d = dist(temp[i],temp[j]);if(d < distance){distance = d;}}}return distance;}double closestDist()//调用分治求解{size_t num = points_vec.size();return calcDist(0,num-1);}};
int main()
{int times;cin >> times;while (times--){ClosestPoint cp;cout << fixed << setprecision(3) << cp.closestDist() << endl;}return 0;
}