分治法求最近点对

本文介绍了一种使用分治策略解决平面中寻找最近点对问题的算法。该算法首先对所有点按x坐标排序,然后递归地将点集分成两半,直至只剩2或3个点。通过比较子集中的点对距离,最终确定最小欧几里得距离的点对。

Dvide and Conquer

Implement the algorithm for the closest pair problem in your favourite language.

INPUT: n points in a plane.

OUTPUT: The pair with the least Euclidean distance.


算法的思想:

首先对所有的点按照x坐标进行从小到大排序,然后利用二分法进行分割,直到区间范围变为2 or 3(即只有2个点或只有3个点),然后可以求这2个点或是3个点的距离(很小的子问题)。

对于每一部分子问题,求其中间mid左右各min范围内的点的距离,如图:

对于这幅图中,只需将绿色的点与右边3个红色的点比较即可(格子按1/2&距离分割,可以保证每个格子内只有1个点)。


对于这幅图,只需将绿色的点与左边6个点比较即可

这里,当我们按照y坐标进行排序后(不考虑x坐标),因此对于每一个点,只需要将其与附近的11个点比较即可。

算法的合并时间:边界附近的点进行排序需要O(nlogn)。

如果每一次迭代保持2个序列,一个按照x进行排序,一个按照y进行排序,像归并排序一样,将预先排序好的2个方向的序列合并起来,实际上只需要0(n)时间。

因此算法的时间复杂度为T(n)=2T(n)+O(n)=0(nlogn)。


input:

-5,-5
-4,-4
-3,-6
-3,-3
-2,-3
-1,-5
0,-3
1,-4
0.5,-3
0.5,-4

output:


#include<iostream>  
#include<string.h>
#include<math.h>
#include <fstream>
#include<iomanip>
#include<algorithm> 
using namespace std;
int k=0; 


struct Record
{
	int c1,c2;
	double ds;
}record[10000];


struct Points
{
	double x,y;
}*points;


double dis(int a,int b)
{
	double dx = points[a].x-points[b].x,dy = points[a].y-points[b].y;
	return sqrt(dx*dx+dy*dy);
}


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

   
int cmpx(const Points &a,const Points &b)    
{  
	if(a.x<b.x)   
		return 1;    
	else  
		return 0;   
}  

int cmpy(const Points &a,const Points &b)    
{  
	if(a.y<b.y)   
		return 1;    
	else  
		return 0;   
}  

void storage(double c,int l,int r)
{	//记录最近点对,用于输出
	record[k].c1 = l;
	record[k].c2 = r;
	record[k++].ds = c;
}


double ClosestPair(int l, int r)
{
	if(1==r-l)
	{
		double d = dis(l,r);
		storage(d,l,r);
		return d;
	}
	if(2==r-l)
	{
		double ll = dis(l,l+1);
		double rr = dis(l+1,r);
		storage(ll,l,l+1);
		storage(rr,l+1,r);
		return min(ll,rr);
	}
	int mid = l+((r-l)>>1);
	double dl = ClosestPair(l,mid);
	double dr = ClosestPair(mid+1,r);
	double Min=min(dl,dr);
	int h1=mid,h2=mid;
	while((points[mid].x-points[h1].x<=Min && h1>=l) || (points[h2].x-points[mid].x<=Min && h2<=r))
	{	//得到mid附近2&距离内的区间范围
		h1--;
		h2++;
	}
	sort(&points[l],&points[r],cmpy); //将mid附近2&距离内的点按照y坐标从小到大排序
	for(int i=h1+1;i<h2;i++)
	{
		int k=(i+11)>h2?h2:(i+11);	//只需要比较附近11个点即可
		for(int j=i+1;j<k;j++)
		{
			double d=dis(i,j);
			if(d>=Min)
				break;
			Min = d;
			storage(d,i,j);
		}
	}
	return Min;
}


int main()
{
	ifstream infile("./points.txt", ios::in); 
    char buf[30];
	double (*data)[2];	
	char *subarr=NULL;
	char *delims = ",";
	int n;
	cout<<"Please input the number of points(eg.input 10 int this test):"<<endl;
	cin>>n;
	points = new struct Points[n];
	data = new double[n][2];
    if(!infile.is_open())
	{
		cout<<"Error opening file!";
		exit(1);
	}
	int i=0,j=0,p=0;
	while(infile.getline(buf,30)!=NULL)
	{
		j = 0;
		subarr = strtok(buf,delims);
		while(subarr!=NULL)
		{
			data[i][j] = atof(subarr);	
			subarr = strtok(NULL,delims);
			j++;
		}
		
		points[p].x = data[i][0];
		points[p++].y = data[i][1];
		i++;
		
	}
	infile.close();
	sort(&points[0],&points[n],cmpx); //按横坐标从小到大排序
	double value = ClosestPair(0,n-1);
	int x,y;
	for(i=0;i<k;i++)
	{
		if(record[i].ds==value)
		{
			
			x = record[i].c1;
			y = record[i].c2;
			cout<<"points("<<points[x].x<<","<<points[x].y<<")and("<<points[y].x<<","<<points[y].y<<")"<<endl;
		}
	}
	cout<<"have the shortest distance :"<<value<<endl;
    return 0;
}


<think>首先,用户的问题是:“我想了解分治法最近点对问题的时间复杂度请问分治法解决最近点对问题的时间复杂度分析”。这涉及到分治法最近点对问题中的应用及其时间复杂度分析。我需要参考系统级指令:-所有行内数学表达式使用$...$格式。-独立公式使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-在回答中引用的段落末尾自然地添加引用标识,例如[^1]。用户可见层的要:-回答结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。用户提供了几个引用:-引用[1]:讨论了分治法最近点对问题的时间复杂度为$O(n\logn)$。-引用[2]:类似地,提到$T(n)=O(n\logn)$。-引用[3]和[4]:关于其他分治法问题的时间复杂度,但用户问题聚焦在最近点对问题。用户的核心问题是关于最近点对问题的时间复杂度分析,所以我应该专注于引用[1]和[2]。关键从引用中提取:-引用[1]:分治法最近点对问题的时间复杂度为$O(n\logn)$。具体来说,$T(n)=2T(n/2)+f(n)$,其中$f(n)$是合并步骤的时间复杂度,为$O(n)$。加上预排序$O(n\logn)$,总时间为$O(n\logn)$。-引用[2]:同样,$T(n)=2T(n/2)+O(n)$,所以$T(n)=O(n\logn)$。现在,构建回答:1.**介绍问题**:简要解释最近点对问题和分治法的应用。2.**分治步骤**:描述算法的主要步骤:分解、解决、合并。3.**时间复杂度分析**:详细分析递归关系。-分解:将集分成两半,递归解。-合并:在合并步骤中,检查跨分割线的对,这可以在线性时间内完成。-递归方程:$T(n)=2T(n/2)+O(n)$。-解递归方程:得到$T(n)=O(n\logn)$。4.**总时间复杂度**:包括预排序步骤(通常需要对按x坐标排序),这需要$O(n\logn)$时间。5.**结论**:总时间复杂度为$O(n\logn)$。使用数学表达式:-行内:如$T(n)=2T(n/2)+O(n)$。-独立公式:如果需要,可以单独显示递归方程。生成相关问题:-基于主题,生成3-4个相关问题。引用标识:-在回答中,当引用信息时,自然地添加[^1]或[^2]。回答结构:-分步骤解释,确保清晰。-使用中文。</think>### 分治法解决最近点对问题的时间复杂度分析 最近点对问题(Closest Pair of Points Problem)的目标是在一个平面上的集中找到距离最近的两个分治法是一种高效的解决策略,它将问题分解为子问题、递归解子问题,并合并结果。时间复杂度分析是算法性能的核心,下面我将逐步解释分治法在该问题中的应用及其时间复杂度。 #### 1. **算法步骤概述** - **分解(Divide)**:将集按x坐标排序后(预排序步骤),平均分成两个子集 $P_{\text{left}}$ 和 $P_{\text{right}}$,分别处理左半部分和右半部分。 - **解决(Conquer)**:递归地在每个子集中最近点对。递归基(base case)是当集大小 $n \leq 3$ 时,直接使用暴力法计算,时间复杂度为 $O(1)$ 或 $O(n^2)$(常数时间忽略)。 - **合并(Combine)**:关键步骤是处理跨分割线的对。在分割线附近(例如,距离分割线小于当前最小距离的区域)查找潜在对,并比较左右子集的解。这一步骤能在 $O(n)$ 时间内完成,因为它只涉及检查有限个(通常最多6个)[^1]。 #### 2. **时间复杂度分析** 分治法的核心是递归方程。令 $T(n)$ 表示处理 $n$ 个的时间复杂度: - **递归方程**:算法将问题分解为两个规模为 $n/2$ 的子问题($2T(n/2)$),合并步骤为 $O(n)$。因此,递归方程为: $$T(n) = 2T(n/2) + O(n)$$ - **解递归方程**: - 使用递归树法或主定理(Master Theorem)解。 - 主定理适用于形式 $T(n) = aT(n/b) + f(n)$ 的方程,其中 $a=2$、$b=2$、$f(n) = O(n)$。 - 因为 $f(n) = O(n) = O(n^{\log_b a}) = O(n^{\log_2 2}) = O(n)$,属于主定理情况二,解为 $T(n) = O(n \log n)$[^1][^2]。 - 递归树分析:递归深度为 $\log_2 n$,每层合并步骤的总时间复杂度为 $O(n)$,因此总时间复杂度为 $O(n \log n)$。 - **预排序时间**:在分治前,集需要按x坐标排序,这通常使用快速排序或归并排序,时间复杂度为 $O(n \log n)$。 - **总时间复杂度**:预排序 $O(n \log n)$ + 分治过程 $O(n \log n)$ = $O(n \log n)$。因此,算法的整体时间复杂度为 $O(n \log n)$[^1][^2]。 #### 3. **为什么合并步骤是 $O(n)$?** - 在合并阶段,算法只在分割线附近的“条带区域”(strip)内检查对,该区域宽度由当前最小距离决定。 - 通过几何性质(如鸽巢原理),条带区域内每个最多需要检查常数个(通常6个),因此合并步骤的时间复杂度为 $O(n)$[^1]。 - 这确保了递归方程 $T(n) = 2T(n/2) + O(n)$ 的正确性。 #### 4. **与暴力法的比较** - 暴力法的时间复杂度为 $O(n^2)$,因为它需要比较所有对。 - 分治法优化到 $O(n \log n)$,显著提升了效率,尤其在大规模集上(例如,$n > 100$ 时优势明显)[^2]。 #### 总结 分治法解决最近点对问题的时间复杂度为 $O(n \log n)$,这归功于高效的递归分解和线性时间的合并步骤。预排序是瓶颈之一,但整体性能优于暴力法。该算法在计算几何中广泛应用,如GIS系统和碰撞检测[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值