最近点对问题

最近点对问题

问题描述:平面上有n个点P1,P2,…,Pn,n>1,Pi的直角坐标为(Xi,Yi),i=1,2,…,n.求距离最近的两个点及他们之间的欧几里得距离。

解析

当2≤n≤3时,问题就可以通过蛮力算法解决。当n>3时,可以利用点集在x轴方向上的中位数m,在该处作一条垂线,将点集分成大小分别为[n/2]和[n/2]的两个子集P1和Pr。即使得其中[n/2]个点位于显得左边或线上,[n/2]个点位于线的右边或线上。然后就可以通过递归求解子问题P1和Pr来的到最近点点对问题的解。其中d1和dr分别表示在P1和Pr中的最近对距离,并定义d = min{d1,dr}
但请注意,d不一定是所有点对的最小距离,因为距离最近的两个点可能分别位于分界线的两侧。因此,在合并较小子问题的解时,需要检查是否有这样的点。显然,我们可以只关注以分割带为对称的、宽度为2d的垂直带中的点,因为任何其他点对的距离都至少为d1
在这里插入图片描述

算法

EfficientClosestPair(P,Q)
//使用分治算法来求解最近点对问题
//输入:数组P中存储了平面上的n≥2个点,并且按照这些点的x轴坐标升序排列
// 数组Q中存储了与P相同的点,只是它是按照这点的y轴坐标升序排列
//输出:最近点对之间的欧几里得距离
if n≤3
 返回由蛮力算法求出最小距离
else
 将P的前[n/2]个点复制到P1
 将Q的前[n/2]个点复制到Q1
 将P中余下的[n/2]个点复制到Pr
 将Q中余下的[n/2]个点复制到Qr
 d1 ← EfficientClosestPair(P1,Q1)
 dr ← EfficientClosestPair(Pr,Qr)
 d ← min(d1,dr)
 M ← P[[n/2]-1].x
 将Q中所有的|x-m|<d的点复制到数组S[0,num-1]
 Dminsq ← d2
 for i ← 0 to num ← 2 do
  k ← i+1
  while k ≤num-1 and (S[k].y-S[i].y)2 < dminsq
   dminsq ← min((S[k].x-S[i].x)2 + (S[k].y-S[i].y)2,dminsq)
   k ← k+1
 return sqrt(dminsq)

代码

#include <ctime>
#include <cmath>
#include <iostream>  
#include <algorithm>

using namespace std;

#define INFINITE_DISTANCE 65535    // 无限大距离
#define COORDINATE_RANGE 100    // 横纵坐标范围为[-100,100]

#ifndef Closest_pair

typedef struct Point
{// 二维坐标上的点Point
	double x;
	double y;
}Point;

double Distance(Point a, Point b)
{//平面上任意两点对之间的距离公式计算
	return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}

bool compareX(Point a, Point b)
{//自定义排序规则:依照结构体中的x成员变量升序排序
	return a.x < b.x;
}

bool compareY(Point a, Point b)
{//自定义排序规则:依照结构体中的x成员变量升序排序
	return a.y < b.y;
}

double min(int a, int b) {
	return a < b ? a : b;
}

double ClosestPair(Point P[], Point Q[], int length, Point &a, Point &b)
{// 求出最近点对记录,并将两点记录再a、b中
	double dminsq,d;                   //记录集合points中最近两点距离 
	double d1, d2;                     //记录分割后两个子集中各自最小点对距离
	int i = 0, j = 0, k = 0, x = 0;    //用于控制for循环的循环变量
	Point a1, b1, a2, b2;              //保存分割后两个子集中最小点对
	
	if (length < 2)
		return INFINITE_DISTANCE;    //若子集长度小于2,定义为最大距离,表示不可达
	else if (length == 2)
	{//若子集长度等于2,直接返回该两点的距离
		a = P[0];
		b = P[1];
		dminsq = Distance(P[0], P[1]);
		return dminsq;
	}
	else if (length == 3) {
		a = P[0];
		b = P[1];
		dminsq = Distance(P[0], P[1]);
		if (Distance(P[1], P[2])< dminsq) {
			a = P[1];
			b = P[2];
			dminsq = Distance(P[1], P[2]);
		}
		if (Distance(P[2], P[0]) < dminsq) {
			a = P[0];
			b = P[2];
			dminsq = Distance(P[0], P[2]);
		}
		return dminsq;
	}
	else {
		//子集长度大于3,进行分治求解
		Point *P1 = new Point[length];     //开辟4个子集
		Point *P2 = new Point[length];
		Point *Q1 = new Point[length];
		Point *Q2 = new Point[length];
		
		

		for (i = 0; i < length / 2; i++)
			P1[i] = P[i];
		for (int j = 0, i = length / 2; i < length; i++)
			P2[j++] = P[i];

		for (i = length / 2; i < length / 2; i++)
			Q1[i] = Q[i];
		for (int j = 0, i = length / 2; i < length; i++)
			Q2[j++] = Q[i];

		d1 = ClosestPair(P1, Q1,length / 2, a1, b1);             //分治求解左半部分子集的最近点  
		d2 = ClosestPair(P2, Q2,length - length / 2, a2, b2);    //分治求解右半部分子集的最近点  
		
		//记录最近点,最近距离
		if (d1 < d2) {
			d = d1; a = a1; b = b1; 
		}            
		else {
			d = d2; a = a2; b = b2; 
		}
		double mid = P[(length / 2) - 1].x;    //中间下标值,即中位数

		Point *S = new Point[length];
		for (i = 0,j=0; i < length; i++) {
			if (fabs(Q[i].x - mid) < d) {
				S[j++].x = Q[i].x;
				S[j].y = Q[i].y;
			}
		}
		dminsq = d * d;

		for (i = 0; i < j-2; i++)
		{
			for (k = i + 1; k <= j - 1 && (S[k].y - S[i].y)*(S[k].y - S[i].y) < dminsq; k++)
			{
				if (dminsq > Distance(S[k], S[i])) {
					dminsq = Distance(S[k], S[i]);
					a = S[i];
					b = S[k];
				}
			}
		}
		return sqrt(dminsq);
	}
	
}

void SetPoints(Point *points, int length)
{//随机函数对点数组points中的二维点进行初始化
	srand(unsigned(time(NULL)));
	for (int i = 0; i < length; i++)
	{
		points[i].x = (rand() % int(COORDINATE_RANGE * 2)) - COORDINATE_RANGE;
		points[i].y = (rand() % int(COORDINATE_RANGE * 2)) - COORDINATE_RANGE;
	}
}

int main()
{
	int num;            //随机生成的点对个数
	Point a, b;            //最近点对
	double diatance;    //点对距离

	cout << "请输入二维点对个数:";
	cin >> num;
	if (num < 2)
		cout << "请输入大于等于2的点个数!!" << endl;
	else
	{
		cout << endl << "随机生成的" << num << "个二维点对如下:" << endl;
		Point *points = new Point[num];
		Point *pointsY = new Point[num];
		SetPoints(points, num);

		memcpy(pointsY, points, sizeof(Point)*num);

		for (int i = 0; i < num; i++)
			cout << "(" << points[i].x << "," << points[i].y << ")" << endl;

		sort(points, points + num, compareX);
		cout << endl << endl << "按横坐标排序后的点对:" << endl;
		for (int i = 0; i < num; i++)
			cout << "(" << points[i].x << "," << points[i].y << ")" << endl;
		
		sort(pointsY, pointsY + num, compareY);
		cout << endl << endl << "按纵坐标排序后的点对:" << endl;
		for (int i = 0; i < num; i++)
			cout << "(" << pointsY[i].x << "," << pointsY[i].y << ")" << endl;

		diatance = ClosestPair(points,pointsY, num, a, b);

		cout << endl << "最近点对为:" << "(" << a.x << "," << a.y << ")和" << "(" << b.x << "," << b.y << ")" << endl << "最近点对距离为:" << diatance << endl;
	}
	system("pause");
}

#endif // !Closest_pair

2

github原码地址


  1. 参考算法设计与分析基础 第3版_(美)ANANY LEVITIN著;潘彦译_北京:清华大学出版社P149 ↩︎

  2. 代码借鉴原作者,稍加修改 ↩︎

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值