算法第三周:平面最近点对(分治算法)

文章描述了一种使用分治思想解决寻找平面上n个点中最近点对的算法。首先按x坐标对点进行排序,然后通过递归将问题分解为左右两个子集,不断查找横坐标接近中垂线的点并计算它们之间的距离,排除不可能产生更短距离的点,最终找到最近点对。
摘要由CSDN通过智能技术生成

Description

给定 n 个平面上的点,求最近两个点的编号。

Input

每组数据第一行 2 ≤ n ≤ 105,接下来 n 行每行两个整数 0 ≤ xi, yi ≤ 104, i = 0, 1, 2, ⋯, n − 1,表示第i个点的坐标。

Output

输出最近两个点的编号 a 和 b,按 a < b 输出。如果有多个答案,输出 a 最小的,如果依然有多个答案,在这些答案里输出 b 最小的。

Sample Input

3
0 0
1 1
3 3

Sample Output

0 1

分治思想:

将P平⾯划分为点个数⼤致相等的两个平⾯ 。把所有点按照x轴坐标升序排序,然后在正中间的位置画一条线,把点分成两个子集P1​和P2,P1和P2分别位于线的左侧和右侧

然后依次递归的处理左区间和右区间,求取点之间的距离,我们定义一个MiniDistance(int left, int right)函数,用来求取最短距离,left和right对应横坐标的区间范围,比如图中就是从x区间[1,10]

首先我们要判断一下递归函数的边界条件,当left==right的时候,相当于要求点到自身的距离,所以我们直接返回,当left+1==right时,刚好在这个范围就只有一个点,所以我们可以求出距离,

//预处理,判断边界条件
if (left == right)return;//如果自己和自己的距离最近,则距离为0,应该返回 
if (left + 1 == right)
{
	//获取距离
	int cur = dis(point[left], point[right]);
	//如果当前距离更小,则更新一下答案
	if (ans >= cur) Update(point[left].id, point[right].id, cur);
	return;
}

其他情况就代表递归没有终止,将继续下去,也就是求出[P1~P5],[P6~P10]中的最近点对,

	int mid = (left + right) >> 1;//如果中间还有其他点,那就再中间分开。 
	MiniDistance(mid, right);//找右边的最小距离 
	MiniDistance(left, mid - 1);//找左边的最小距离 


 当这个递归函数返回到最上层时,我们能拿到这样的一个结果

左半区间的最短长度是d1,右半区间的最短长度是d2,然后我们选择一个在d1和d2中选择更小的那个,即min(d1,d2)

但这并不是我们的最终结果,如同其他分治策略一样,我们要考虑横跨左右区间的情况 ,比如P3-P8

但是显然P3-P8显然距离太大了,有没有什么方法我们能排除一些点,来避免不必要的计算呢?

我们可以先计算每个点到中垂线的横坐标的距离,如果它们的距离小于当前的distance,那么它们就是有潜力成为最短距离的点,我们用一个数组temp[]保存下来,图中标红的点就是

 

为啥其它点不用考虑呢?因为其它点离该线的距离都大于等于 d 了,就更不用说到另一侧的点的距离了,必定不如直接返回 d 来的优

int k = 0;
	for (int i = left; i <= right; i++)
	{
		if (fabs(point[mid].x - point[i].x) <= ans)
		{
			temp[++k] = i;
		}
	}//有可能在两个平面中间还有其他的更小距离 这个就是按照x坐标找可能出现的点 

 然后我们就可以计算这些点之间的距离了

sort(temp + 1, temp + k + 1, cmp2);//按这些点的y坐标升序排列,目的是为了方便接下来的计算
	//线性扫描,找这些点的最小距离 
	for (int i = 1; i <= k; i++)
	{
		for (int j = i + 1; j <= k && point[temp[j]].y - point[temp[i]].y <= ans; j++)//注意第二个判断条件
		{
			if (ans >= dis(point[temp[i]], point[temp[j]]))
			{
				int new_distance = dis(point[temp[i]], point[temp[j]]);
				Update(point[temp[i]].id, point[temp[j]].id, new_distance);
			}
		}
	}
	return;

 注意到在for循环中有一个表达式: point[temp[j]].y - point[temp[i]].y <= ans,这个是什么意思呢?

和上面同理,如果两者的纵坐标之差都大于等于 d 了,那再计算就没有丝毫意义了,因为如果加上水平距离的话,必定不如直接返回 �d 来的优

例如 P3​ 号点,它只需和 P4​ 匹配就行了,因为它们的纵坐标之差为 0,有可能会带来更好的答案,而它和 P7​ 匹配就没有任何意义,因为即便它们的纵坐标之差等于 d,但就算 P7​ 在 P3​ 的正上方,距离也不会小于 d。

完整代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
#define Inf 1<<31-1
struct node {
	double x;
	double y;
	int id;//点的编号
};
struct node point[200005];
int temp[300005];
int a = 2147483647, b = 2147483647, ans = 2147483647;
double dis(node a, node b)
{
	return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
bool cmp1(node a, node b)
{
	if (a.x == b.x)return a.y < b.y;
	return a.x < b.x;
}//第一个排序是按照x坐标升序,排序以后才好分左右平面 
bool cmp2(int a, int b)
{
	return point[a].y < point[b].y;
}//第二个排序是选择中间的点,按y坐标升序排列


void Update(int new_a, int new_b, int new_ans)
{
	if (new_a > new_b) swap(new_a, new_b);
	if (new_ans == ans)
	{
		if (a > new_a || a == new_a && b > new_b)
		{
			a = new_a;
			b = new_b;
		}
	}
	else { a = new_a; b = new_b; ans = new_ans; }
}
void MiniDistance(int left, int right)
{
	if (left == right)return;//如果自己和自己的距离最近,则距离为0,应该返回 
	if (left + 1 == right)
	{
		//获取距离
		int cur = dis(point[left], point[right]);
		//如果当前距离更小,则更新一下答案
		if (ans >= cur) Update(point[left].id, point[right].id, cur);
		return;
	}
	int mid = (left + right) >> 1;//如果中间还有其他点,那就再中间分开。 
	MiniDistance(mid, right);//找右边的最小距离 
	MiniDistance(left, mid - 1);//找左边的最小距离 
	int k = 0;
	for (int i = left; i <= right; i++)
	{
		if (fabs(point[mid].x - point[i].x) <= ans)
		{
			temp[++k] = i;
		}
	}//有可能在两个平面中间还有其他的更小距离 这个就是按照x坐标找可能出现的点 
	sort(temp + 1, temp + k + 1, cmp2);//按这些点的y坐标升序排列
	//线性扫描,找这些点的最小距离 
	for (int i = 1; i <= k; i++)
	{
		for (int j = i + 1; j <= k && point[temp[j]].y - point[temp[i]].y <= ans; j++)//注意第二个判断条件
		{
			if (ans >= dis(point[temp[i]], point[temp[j]]))
			{
				int new_distance = dis(point[temp[i]], point[temp[j]]);
				Update(point[temp[i]].id, point[temp[j]].id, new_distance);
			}
		}
	}
	return;
}
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> point[i].x >> point[i].y;
		//点的序号
		point[i].id = i - 1;
	}
	//按横坐标升序排列
	sort(point + 1, point + n + 1, cmp1);
	MiniDistance(1, n);
	cout << a << " " << b;
	return 0;
}

  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值