数字之魅:寻找二维平面上的最近的点对

在二维平面上的n个点中,如何快速的找出最近的一对点,就是最近点对问题。

初看这个题,可能感觉有点儿复杂。

方案一:蛮力法。数组中总共包含N个数,所以我们可以把平面内所有的点按X轴排序,然后依次算出后一个坐标与前面所有左边的距离,然后用Min和position来记录最近的距离和两个坐标。该方案和在一维空间求两个最近点的距离有点儿类似,其时间复杂度为:O(N*N).

方案二:在一维空间里,我们知道如果数组有序,我们可以很快找出最近的两个点。我们可以用O(N*logN)的时间复杂度来对数据进行排序[快,堆,归]。排完序后只需要O(N)的时间复杂度就可以得到最小的差值。但是该方法不能用在二维,因为在二维空间中,X轴间距离最小的两个点距离不一定是最短的,如下图所示。

那还有什么方法吗?这里我们采用了分治法。我们利用数组的中位数[中位数的求解细节可以参考这里],将数组分成Left和Right两个部分,要么来自Right部分,要么来自Left部分,或者来自Left和Right。这里我们就可以将时间复杂度降低到O(N*logN)。

这里主要复杂在合并的地方,其思想如下:

第一步:首先找出点集数据中的中位数median,按X坐标来划分;用median对点集数据进行划分,左边的为data1,右边的为data2;

第二步:对分成的两部分分别求出data1和data2中的最近点对,记为:MinDis1和MinDis2;

第三步:求出data1和data2最近点对距离的较小值:MinDis = min{MinDis1,MinDis2};

第四步:找出data2中y值前6大的点[利用鸽巢原理,因为其距离不可能小于Min,而我们只考虑在Min*2*Min的方框内的数据],

对于data1中的点,与data2中的每一个点计算距离MinDisO, 如果MinDisO < MinDis,就改变MinDis的值,MinDis=MinDis0;说明在最小的距离一个来自左边一个来自右边,在合并的时候产生。

参考代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
// 顶点信息
struct Point
{
	double m_x, m_y;
	Point():m_x(0.0),m_y(0.0) {}
	Point(double x, double y):m_x(x),m_y(y){}
	bool operator==(const Point& p) const
	{return m_x==p.m_x && m_y==p.m_y;}
};  

ostream& operator<<(ostream& os, const Point& p)
{
	return os << "(" << p.m_x << "," << p.m_y << ")";
}  

// 插入排序
template<class T, class Pr>
void insert_sort(vector<T> &vec, int l, int r, Pr pred)
{
	int i, j;
	for (i=l+1; i<=r; i++)
	{
		T tmp = vec[i];
		for (j=i-1; j>=l && pred(tmp,vec[j]); j--)
			vec[j+1]=vec[j];
		vec[j+1] = tmp;
	}
}  

// 找到key所在的位置
template<class T>
int get_position(vector<T> &vec, int l, int r, T key)
{
	for (int i=l; i<=r; i++)
		if (key == vec[i])
			return i;
	return -1;
}  

// 按第一个元素对vec进行划分
template<class T, class Pr>
int partition(vector<T> &vec, int l, int r, Pr pred)
{
	int i, j;
	for (i=l+1,j=l; i<=r; i++)
	{
		if (pred(vec[i],vec[l]))
		{
			++j;
			swap(vec[i],vec[j]);
		}
	}
	swap(vec[j],vec[l]);
	return j;
}  

// 顺序统计得到第k个元素的值
template<class T, class Pr>
T select(vector<T> &vec, int l, int r, int k, Pr pred)
{
	int n = r-l+1;
	if (n==1)
	{
		if (k!=0)
			printf("Out of Boundary!\n");
		return vec[l];
	}
	// 找中位数的中位数作为分割点
	int cnt = n/5;
	int tcnt = (n+4)/5;
	int rem = n%5;
	vector<T> group(tcnt);
	int i, j;
	for (i=0,j=l; i<cnt; i++,j+=5)
	{
		insert_sort(vec, j, j+4, pred);
		group[i] = vec[j+2];
	}
	if (rem)
	{
		insert_sort(vec, j, j+rem-1, pred);
		group[i] = vec[j+(rem-1)/2];
	}
	T key = select(group, 0, tcnt-1, (tcnt-1)/2, pred);
	// 找到分割点的位置
	int key_pos = get_position(vec, l, r, key);
	swap(vec[key_pos], vec[l]);
	// 用分割点对数组进行划分,小的在左边,大的在右边
	int pos = partition(vec, l, r, pred);
	int x = pos - l;
	if (x == k) return key;
	else if (x < k)
		return select(vec, pos+1, r, k-x-1, pred);
	else
		return select(vec, l, pos-1, k, pred);
}  

// 计算点a和b的距离
double dist(const Point& a, const Point& b)
{
	double x = a.m_x-b.m_x;
	double y = a.m_y-b.m_y;
	return sqrt(x*x+y*y);
}  

bool cmpX(const Point& a, const Point& b)
{
	return a.m_x < b.m_x;
}  

bool cmpY(const Point& a, const Point& b)
{
	return a.m_y < b.m_y;
}  

double minDifferent(vector<Point> p, int l, int r, vector<Point> &result)
{
	// 按中位数进行划分后的子区域的元素个数都会减小到2或3,不会再到1
	if ((r-l+1)==2)
	{
		result[0] = p[l];
		result[1] = p[r];
		if (cmpX(p[r],p[l])) swap(p[l], p[r]);
		return dist(p[l], p[r]);
	}
	if ((r-l+1)==3)
	{
		insert_sort(p, l, r, cmpX);
		double tmp1 = dist(p[l], p[l+1]);
		double tmp2 = dist(p[l+1], p[l+2]);
		double ret = min(tmp1, tmp2);
		if (tmp1 == ret)
		{
			result[0] = p[l];
			result[1] = p[l+1];
		}
		else
		{
			result[0] = p[l+1];
			result[1] = p[l+2];
		}
		return ret;
	}
	// 大于3个点的情况
	int mid = (r+l)>>1;
	Point median = select(p, l, r, mid-l, cmpX);
	vector<Point> res1(2), res2(2);
	double min_l = minDifferent(p, l, mid, res1);
	double min_r = minDifferent(p, mid+1, r, res2);
	double minum = min(min_l, min_r);
	if (minum == min_l)
	{
		result[0] = res1[0];
		result[1] = res1[1];
	}
	else
	{
		result[0] = res2[0];
		result[1] = res2[1];
	}
	// 对[p[mid+1]-minum, p[mid]+minum]的带状区域按y排序
	vector<Point> yvec;
	int i, j;
	for (i=mid+1; i<=r; i++)
		if (p[i].m_x - p[mid].m_x < minum)
			yvec.push_back(Point(p[i]));
	for (i=mid; i>=l; i--)
		if (p[mid+1].m_x - p[i].m_x < minum)
			yvec.push_back(Point(p[i]));
	sort(yvec.begin(), yvec.end(), cmpY);
	for (i=0; i<yvec.size(); i++)
	{
		// 至多只有与其后最多7个点的距离会小于minum
		for (j=i+1; j<yvec.size() && yvec[j].m_y-yvec[i].m_y<minum &&
			j<=i+7; j++)
		{
			double delta = dist(yvec[i],yvec[j]);
			if (delta < minum)
			{
				minum = delta;
				result[0] = yvec[i];
				result[1] = yvec[j];
			}
		}
	}
	return minum;
}  

int main()
{
	int n, i, j, x, y;
	vector<Point> result(2);
	vector<Point> input;
	cout<<"please input the number of your data:"<<endl;
	cin >> n;
	cout<<"please input your data:"<<endl;
	for (i=0; i<n; i++)
	{
		cin >> x;
		cin >> y;
		input.push_back(Point(x,y));
	}
	double minum = minDifferent(input, 0, input.size()-1, result);
	cout << "nearest point: " << result[0] << " and "
		<< result[1] << endl;
	cout << "distance: " << minum << endl;
	system("pause");
	return 0;
}  

运行结果:

时间: 2024-08-04 11:33:07

数字之魅:寻找二维平面上的最近的点对的相关文章

9.7数学与概率(三)——在二维平面上,有两个正方形,请找出一条直线,能够将这两个正方形对半分

/** * 功能:在二维平面上,有两个正方形,请找出一条直线,能够将这两个正方形对半分. * 假定正方形的上下两条边与x轴平行. */ /** * 考虑: * 线的准确含义,可能性有: * 1)由斜率和y轴截距确定: * 2)由这条边上的任意两点确定: * 3)线段,以正方形的边作为起点和终点. * * 假设:这条线的端点应该落在正方形的边上. * 思路:要将两个正方形对半分,这条线必须连接两个正方形的中心点. */ public class Square { //正方形的四条边 int lef

9.7数学与概率(四)——在二维平面上,有一些点,请找出经过点数最多的那条线

/** * 功能:在二维平面上,有一些点,请找出经过点数最多的那条线. /** * 思路:在任意两点之间画一条无线长的直线,用散列表追踪那条直线出现的次数最多.时间复杂度O(N*N) * 注意: * 1)用斜率和y轴截距来确定是否是同一条直线. * 2)浮点数不一定能用二进制数准确表示,因此检查两个浮点数的差值是否在某个极小值(epsilon)内. * 3)对于散列表而言,斜率相等,未必散列值相同.因此,将斜率减去一个极小值,并以得到的结果flooredSlope作为散列键. * 4)取得所有可

编写一个表示二维平面上的点的类MyPoint,满足以下条件: 1、定义private的成员变量x和y,表示点的x和y坐标,类型为double

编写一个表示二维平面上的点的类MyPoint,满足以下条件:1.定义private的成员变量x和y,表示点的x和y坐标,类型为double2.定义两个MyPoint的构造方法,一个构造方法不带参数,而且x和y的初始值为0,另一个构造方法有两个参数,参数名为x和y,类型为double,用这两个参数分别作为初始x和y坐标3.定义一个getD方法,有一个类型为MyPoint的对象参数,功能为返回当前对象和参数对象这两个坐标点的距离,返回值为double类型4.编写测试的main方法,调用getD计算两

第2章 数字之魅——寻找最近点对

寻找最近点对 问题描述 给定平面上N个点的坐标,找出距离最近的两个点. 分析与解法 我们不妨先看一看一维的情况:在一个包含N个数的数组中,如何快速找出N个数中两两差值的最小值?一维的情况相当于所有的点都在一条直线上.虽然是一个退化的情况,但还是能从中得到一些启发. [解法一] 数组中共包含N个数,我们把它们两两之间的差值都求出来,那样就不难得出最小的差值了.这样一个直接的想法,时间复杂度为O(N2).具体代码如下: 1 package chapter2shuzizhimei.findminpoi

第2章 数字之魅——求二进制中1的个数

求二进制中1的个数 问题描述 对于一个字节(8bit)的变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能地高. [解法一] 可以举一个八位的二进制例子来进行分析.对于二进制操作,我们知道,除以一个2,原来的数字将会减少一个0.如果除的过程中有余,那么就表示当前位置有一个1. 以10 100 010为例: 第一次除以2时,商为1 010 001,余为0. 第二次除以2时,商为101 000,余为1. 因此,可以考虑利用整型数据除法的特点,通过相除和判断余数的值来进行分析.于是有了如下的

第2章 数字之魅——寻找数组中的最大值和最小值

寻找数组中的最大值和最小值 问题描述 对于一个由N个整数组成的数组,需要比较多少次才能把最大和最小的数找出来呢? 分析与解法 [解法一] 可以把寻找数组中的最大值和最小值看成是两个独立的问题,我们只要分别求出数组的最大值和最小值即可解决问题.最直接的做法是先扫描一遍数组,找出最大的数以及最小的数.这样,我们需要比较2*(N-1)次才能找出最大的数和最小的数.代码如下: 1 package chapter2shuzizhimei.findminmax; 2 /** 3 * 寻找数组中的最大值和最小

数字之魅——寻找发帖水王

寻找发帖水王这个题目给了我很大的启发,同时开阔了视野,往往在解决这类型问题的时候第一想法都是先排序再计算.而本题却给出了一个非常好的思路,时间复杂度为O(N). 它还有一个扩展问题,但是我在网上看了几篇有的写的考虑不周全,有的写的逻辑不是很清楚,这里我也根据思考和查阅,给出我自己的解法. typedef int Type; //给出Id的一个抽象 Type candidate[3] = {0}; //说明传的这个参数前要初始化. void find3(Type* ID,int N,Type* c

二维平面上判断点是否在三角形内

最近在项目中碰到的这个问题,在此记录一下.已知三角形的三个顶点坐标,判断某个点是否在三角形中(在三角形的边上,我们也视作在三角形中),本文给出了三种方法.   算法1 利用面积法,如上图所示,如果点P在三角形ABC的内部,则三个小三角形PAB, PBC, PAC的面积之和 = ABC的面积,反之则不相等. 已知三角形的三个顶点坐标求其面积,可以根据向量的叉乘,参考here. 该算法详见后面的函数:IsPointInTriangle1   算法2 首先看一下这个问题,如何判断某两个点在某条直线的同

第2章 数字之魅——寻找发帖“水王”

寻找发帖“水王” 问题描述 Tango是微软亚洲研究院的一个试验项目.研究院的员工和实习生们都很喜欢在Tango上面交流灌水.传说,Tango有一大"水王",他不但喜欢发贴,还会回复其他ID发的每个帖子.坊间风闻该"水王"发帖数目超过了帖子总数的一半.如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗? 解法 采用Map存储每个ID和它出现的次数,之后遍历一遍Map找出其中的“水王”,时间复杂度为O