10245 - The Closest Pair Problem

本文介绍了一个使用分治策略解决寻找坐标系中最近点对距离的问题。通过两个循环进行初步查找,并利用分治法优化算法效率,最终输出最小距离。此方法适用于大量坐标数据集。
/*
推荐题型:三星,最近点对问题,典型分治法问题

题意:N为坐标个数,随后输入N个坐标,输出距离最近的两坐标的距离

分治策略:原来做这道题的时候观察n<10000,所以效率为N*N时也可以,想到了使用两个循环,然后剪枝,通过。
后来看别人代码,发现原来可以使用分治的,典型的分治问题,修改后的代码见方法二:
*/

#include <cstdio>
#include <cstdlib>
#include <cmath>
const int nMax=10007;
const double INF=40000*40000;
struct Node
{
	double x;
	double y;
}node[nMax];
int N;
double min;
int cmp(const void *a, const void *b)
{
	Node *pa = (Node *)a;
	Node *pb = (Node *)b;
	return pa->x > pb->x ? 1 : -1;
}
double cal(int i, int j)
{
	double a = node[i].x - node[j].x;
	double b = node[i].y - node[j].y;
	return sqrt(a * a + b * b);
}
void solve()
{
	int i, j;
	for(i = 0; i < N - 1; ++ i)
		for( j = i + 1;j < N; ++ j)
		{
			double res = cal(i, j);
			if(node[j].x - node[i].x >= min)
				break;
			if(res < min)
				min = res;
		}
}
int main()
{
	//freopen("f://data.in", "r", stdin);
	while(scanf("%d", &N) && N)
	{
		for(int i = 0; i < N; ++ i)
			scanf("%lf %lf", &node[i].x, &node[i].y);
		qsort(node, N, sizeof(node[0]), cmp);
		min=INF;
		solve();
		if(min < 10000)
			printf("%.4lf\n", min);
		else
			printf("INFINITY\n");
	}
	return 0;
}



//方法二:分治法实现。
#include <cstdio>
#include <cstdlib>
#include <cmath>
const int nMax = 10007;
const double INF = 40000 * 40000;
struct Node
{
	double x;
	double y;
}node[nMax];
int N;
double cal(int i, int j)
{
	double a = node[i].x - node[j].x;
	double b = node[i].y - node[j].y;
	return sqrt(a * a + b * b);
}
double bsearch(int a, int b)
{
	if(1 == b - a) return INF;
	int mid = a + (b - a) / 2;
	double min1 = bsearch(a, mid);
	double min2 = bsearch(mid, b);
	double min = min1 < min2 ? min1 : min2;
	int i, j;
	for(i = mid - 1; i >= a; -- i)
		for(j = mid; j < b; ++ j)
		{
			if(node[j].x - node[i].x >= min)
				break;
			if(cal(i, j) < min)
				min = cal(i, j);
		}
	return min;
}
int cmp(const void *a, const void *b)
{
	Node *pa = (Node *)a;
	Node *pb = (Node *)b;
	return pa->x > pb->x ? 1 : -1;
}
int main()
{
	//freopen("f://data.in", "r", stdin);
	while(scanf("%d", &N) && N)
	{
		for(int i = 0; i < N; ++ i)
			scanf("%lf %lf", &node[i].x, &node[i].y);
		qsort(node, N, sizeof(node[0]), cmp);
		double min=bsearch(0,N);
		if(min < 10000)
			printf("%.4lf\n", min);
		else
			printf("INFINITY\n");
	}
	return 0;
}

<think>我们首先需要知道ClosestPoints函数的具体实现。通常,ClosestPoints函数用于在二维点集中找到最近的一对点(即最近点对问题)。常用的算法是分治法,其时间复杂度为$O(n \log n)$。 假设我们使用的是分治法,那么ClosestPoints函数的主要步骤包括: 1. 将点集按照x坐标排序。 2. 将点集分成左右两个子集。 3. 递归地在左右子集中求解最近点对,得到左右子集中的最小距离d1和d2,取d = min(d1, d2)。 4. 然后考虑一个点在左子集,一个点在右子集的情况。此时,只需要考虑分割线两侧距离为d的带状区域内的点。 5. 对于带状区域内的点,按照y坐标排序,然后检查每个点其后续几个点(通常最多6个)的距离,更新最小距离。 现在,我们来分析这个分治算法的时间复杂度。 基本语句(关键操作)是计算两点之间距离的语句。 时间复杂度的递推关系为: $$ T(n) = 2T(n/2) + O(n) $$ 这是因为: - 递归地解决两个子问题,每个子问题规模为n/2,因此为2T(n/2)。 - 合并步骤(即考虑跨越左右子集的点对)需要O(n)时间。这是因为在带状区域内,我们按照y坐标排序(可以使用归并排序,在O(n)时间内合并两个已按y排序的子集),然后每个点只需要常数个(最多6个)后续点进行比较。 因此,根据主定理,这个递推式的解为$T(n)=O(n \log n)$。 但是,我们还需要注意,在分治之前,我们需要对点集按照x坐标进行一次排序,这需要$O(n \log n)$时间。由于递归过程中每一层合并时都需要对y坐标进行排序?实际上,在分治算法的合并步骤,为了得到按y排序的数组,我们可以在递归时同时返回按y排序的子数组,这样合并两个已排序的数组只需要O(n)时间。因此,整个算法的时间复杂度主要由最初的排序决定,为$O(n \log n)$。 现在,我们具体分析基本语句(即计算两点距离的语句)的执行次数。 在递归的每一层,合并步骤中,对于带状区域内的每个点,我们最多检查它后面的6个点(因为根据鸽巢原理,在d×d的矩形区域内最多只能有6个点,使得任意两点之间的距离小于d)。因此,合并步骤中计算距离的次数最多为6n,即O(n)。 因此,基本语句(计算距离)的总次数为: - 递归树有O(log n)层。 - 每一层的合并步骤中,基本语句执行次数为O(n)(因为每一层的点集总数为n,而合并步骤中每个点最多比较6次)。 - 所以总的基本语句执行次数为O(n log n)。 另外,初始排序(按x坐标)需要O(n log n)次比较操作,这比距离计算的操作次数多,但是不同的基本操作。如果我们关注的是距离计算的次数,那么它是O(n log n)次。 结论:ClosestPoints函数中,基本语句(两点距离计算)的渐进时间复杂度为$O(n \log n)$。 下面我们用伪代码描述分治算法的最近点对问题,并标出基本语句: ``` ClosestPoints(Px, Py): // Px是按x排序的点集,Py是按y排序的点集 如果点集大小小于等于3: 使用暴力方法计算最近点对 返回最近点对的距离 将Px分成左右两个子集:左子集Lx和右子集Rx(中点mid) 从Py中分离出左子集的Ly(按y排序)和右子集的Ry(按y排序) --> 这一步可以在O(n)完成 d1 = ClosestPoints(Lx, Ly) d2 = ClosestPoints(Rx, Ry) d = min(d1, d2) 构建带状区域:所有x坐标在[mid.x-d, mid.x+d]的点,记为S 对S按照y坐标排序(实际上,我们可以通过遍历Py,选择符合条件的点,这样是O(n)得到,但是为了得到按y排序,我们可以直接使用Py,因为Py是整个点集按y排序的,所以S在y方向也是有序的?不,Py是整个点集按y排序的,但是S是带状区域的点,这些点在y方向是无序的。所以我们需要对S按y排序,这一步需要O(|S|log|S|)?但是实际上,我们可以通过另一种方式:在递归过程中,我们保持了左右子集的y排序,那么合并时,我们可以从Ly和Ry中提取带状区域的点,然后归并,这样是O(n)的。所以整个算法可以避免在合并步骤使用排序。 实际上,在经典的算法中,我们在递归之前已经将整个点集按照y排序,并且在递归过程中维护了按y排序的左子集和右子集(通过拆分Py)。这样,在合并步骤,我们可以直接得到按y排序的带状区域点集S(因为S是从Ly和Ry中提取符合条件的点,然后合并成一个按y排序的数组,由于Ly和Ry已经有序,所以可以用O(n)时间合并)。 然后,遍历S中的每个点: 对于每个点p,检查S中后续的7个点(实际最多6个)p的距离 --> 基本语句:计算两点之间的距离 如果发现更小的距离,更新d。 返回d ``` 然而,上述伪代码中,我们注意到在合并步骤中,我们并没有对S进行排序操作,而是通过递归时维护的按y排序的数组,在O(n)时间内得到了按y排序的S。因此,合并步骤的时间复杂度为O(n)。 但是,初始排序(按x和按y)都是在递归之前完成的,需要O(n log n)时间。 所以,整个算法的时间复杂度为O(n log n)。 基本语句(计算两点之间距离的语句)的执行次数: - 递归的每一层,在合并步骤中,我们检查带状区域S内的每个点,每个点最多检查后续7个点(常数个),所以合并步骤中距离计算的次数为O(|S|),而|S|最多为n(虽然通常远小于n,但是最坏情况下可能达到n)。因此,每一层合并步骤中基本语句执行次数为O(n)。 - 递归树有log n层,所以总的基本语句执行次数为O(n log n)。 因此,渐进时间复杂度为$O(n \log n)$。 注意:在递归的底层(点数<=3)时,我们使用暴力方法,此时常数次距离计算(最多3次),所以不影响渐进分析。 总结:ClosestPoints函数中,时间复杂度渐进分析的基本语句(距离计算)的次数为$O(n \log n)$。 引用说明: 我们参考了渐进分析的基本概念[^2][^3],时间复杂度的大O表示法[^1]。 </think>### ClosestPoints 函数时间复杂度渐进分析 在最近点对问题(Closest Points Problem)中,ClosestPoints 函数的时间复杂度分析主要关注以下关键语句: #### 1. **距离计算语句** ```python dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) ``` 这是算法中最基础的操作,两点欧氏距离计算的时间复杂度为 $O(1)$。 --- #### 2. **分治策略的递归结构** ```python left_min = closest_points(left_half) # T(n/2) right_min = closest_points(right_half) # T(n/2) min_dist = min(left_min, right_min) ``` 此部分满足递推关系: $$ T(n) = 2T\left(\frac{n}{2}\right) + O(\text{合并步骤}) $$ --- #### 3. **带状区域合并操作** ```python strip_points = sorted(strip_points, key=lambda point: point[1]) # O(n log n) for i in range(len(strip_points)): j = i + 1 while j < len(strip_points) and (strip_points[j][1] - strip_points[i][1]) < min_dist: # O(1) 常数级 dist = distance(strip_points[i], strip_points[j]) min_dist = min(min_dist, dist) j += 1 ``` 这是时间复杂度的关键部分: - **排序操作**:$O(n \log n)$ - **双指针扫描**:每个点最多比较 7 次(鸽巢原理证明 [^1]),时间复杂度 $O(7n) = O(n)$ --- #### 4. **整体时间复杂度分析** 根据主定理(Master Theorem): $$ T(n) = 2T\left(\frac{n}{2}\right) + O(n \log n) $$ 其中: - $a = 2$(子问题数量) - $b = 2$(分割比例) - $f(n) = n \log n$(合并成本) 满足主定理**情况 3**: ∵ $f(n) = \Omega(n^{\log_2 2 + \epsilon}) = \Omega(n^{1+\epsilon})$ 且 $2f(n/2) \leq kf(n)$ 成立 &there4; $T(n) = \Theta(f(n)) = \Theta(n \log n)$ --- #### 时间复杂度总结 | 步骤 | 时间复杂度 | 说明 | |------|-----------|------| | 初始排序 | $O(n \log n)$ | 按 x 坐标预排序 | | 递归分解 | $2T(n/2)$ | 分治结构 | | 带状区合并 | $O(n \log n)$ | 排序主导步骤 | | 总复杂度 | $O(n \log n)$ | 最优解 [^2] | > **关键结论**:合并步骤中的 $O(n \log n)$ 排序操作是时间复杂度瓶颈,决定了整体复杂度为 $O(n \log n)$,优于暴力解法的 $O(n^2)$[^3]。 --- ### 相关问题 1. 分治算法中带状区域为何只需检查 7 个相邻点?如何证明?[^1] 2. 能否优化合并步骤的排序操作使复杂度降至 $O(n)$? 3. 三维空间中的最近点对问题时间复杂度如何分析? 4. 如何处理动态点集的最近点对更新问题? [^1]: 基于鸽巢原理,在 $d×2d$ 矩形区域内最多只能存在 8 个点(实际证明为 7 个) [^2]: 分治策略下最近点对问题的最优复杂度证明 [^3]: 暴力解法需计算 $C(n,2)$ 个点对距离
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值