【分治算法】最近邻近点对MinDistance()

例题:设平面上有n个点p_{1}p_{2},...,p_{n},n>1,p_{i}的直角坐标是(x_{i}y_{i}),i=1,2,...,n,求距离最近的两个点及它们之间的距离。

最短距离公式:      d(p_{i},{p_{j}})=\sqrt{(x_{i}-x_{j})^{2}+(y_{i}-y_{j})^{2}}

用蛮力算法需要计算每两个点之间的距离,并比较出最短距离,那么就有n(n-1)/2个点对,需要O(n^{2})的时间。


分治算法

初步思想:用一个竖线L将整个平面内p集合划分成左右两个平面p_{L}p_{R},使左右平面各有将近\frac{|p|}{2}

p中的最邻近点有三种情况:

  1. 两个点都在p_{L}
  2. 两个点都在p_{R}
  3. 一个点在p_{L}中,另一个在 p_{R}

对于前两种情况,可以分别计算p_{L}p_{R}中的最邻近点对,这是两个2/n的子问题。

对于第三种情况,假设p_{L}p_{R}中最邻近点对之间的距离为\delta _{L}\delta _{R},令\delta =min\left \{ \delta _{L},\delta _{R} \right \},那么在 p_{L}p_{R} 中的任意两点间距离都小于等于\delta,这就是说,如果出现了第三种情况,那么这一点对的距离也不能超过\delta,因此,为找到这两点,只需要考虑直线L两边不超过\delta的窄缝即可。

伪代码如下:MinDistance\left (P,X,Y \right )

输入:n个点的集合P,X和Y分别给出P中点的横、纵坐标
输出:最近的两个点及距离

  1. 如果P中点数小于等于3,直接给出最短距离
  2. 排序X,Y(X:横坐标集合,Y:纵坐标集合)
  3. 做垂线L将p近似划分为大小相等点集 p_{L}p_{R} 
  4. MinDistance\left (P_{L},X_{L},Y_{L} \right )     //递归计算左半平面最邻近点对
  5. MinDistance\left (P_{R},X_{R},Y_{R} \right )    //递归计算右半平面最邻近点对
  6. \delta\leftarrow \left \{ \delta _{L},\delta _{R} \right \}
  7. 对于在直线L左边距离\delta范围内的每个点,检查L右边是否有点与它的距离小于\delta,若有,更新\delta

 该算法的时间复杂度的递推方程为:

\begin{cases} T(n)=2T(\frac{n}{2})+O(n)\\ T(n)=O(1) \quad\quad\quad\quad\quad n\leq 3 \end{cases}

 C语言算法实现如下:

//例2.5 MinDistance(P,X,Y)
//输入:n个点的集合P,X和Y分别给出P中点的横、纵坐标
//输出:最近的两个点及距离
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>

#define INF 2147483647
#define N 10000
struct node {
 double x;
 double y;
}point[N];

//调用结构体的快排 
int cmp_px(const void* a, const void* b)
{
	struct node aa = *(struct node*)a;
	struct node bb = *(struct node*)b;
	if (aa.x != bb.x)
		return aa.x - bb.x;//按照x从小到大的顺序排序
	else 
		return aa.y - bb.y;//当x相等的时候,按照y的从大到小的顺序排序 
}

//计算两点的距离的函数 
double distance(int start, int end)
{
 	return sqrt((point[start].x - point[end].x) * (point[start].x - point[end].x) + ((point[start].y - point[end].y) * (point[start].y - point[end].y)));
}

double MinDistance(int start, int end)
{
	//若范围内只有一个点,则返回无穷大
	if (start == end) 
	{
  		return INF;
 	}
 	//若范围中有两个点,这两个点的距离即为最小距离
	else if (start == end - 1) 
	{
  		return distance(start,end);
 	}
 	//若范围内有三个点,则遍历求出两两点之间的距离比较大小 
 	else if(start == end - 2)
 	{
 		double a,b,c;
 		int mid = (start + end) / 2;
 		a = distance(start,mid);
 		b = distance(start,end);
 		c = distance(mid,end);
 		int min = a;
 		if(b < min)
 			min = b;
 		else if(c < min)
 			min = c;
 		return min;
	}
 	//大于三个点的时候
 	else
 	{
 		double mdistance;
 		int i = 0, j = 0, k = 0;
 		
 		int mid = (start + end) / 2;
	 	double left = MinDistance(start, mid);//左边递归
	 	double right = MinDistance(mid + 1, end);//右边递归
	 	
	 	mdistance = left < right ? left : right;//比较左右两边的最短距离,找出最小值 
	 	
	 	int temp[N]={ 0 };
		
		for(i=start;i<=end;i++)
		{
			if(fabs(point[mid].x - point[i].x) <= mdistance)
			{
				temp[k++] = i;
			}
		}
		for (i = 0; i <= k - 1; i++)//纵坐标寻找最短距离 
	 	{
	 		for (j = i + 1; j <= k - 1 && j < i + 7; j++)
	 		{
	 			if (fabs(point[temp[j]].y - point[temp[i]].y) < mdistance)
				{
	 			mdistance = mdistance < distance(temp[i], temp[j]) ? mdistance : distance(temp[i], temp[j]);
				}
			}
	 	}
		 return mdistance; 
	}
}
 
 
int main()
{
	int n = 1;
 	int i; 
 	printf("请输入集合点个数:") ; 
 	scanf_s("%d", &n);
 	printf("输入原始数据:\n") ;
	for (i = 0; i < n; i++)
	{ 
		scanf("%lf %lf", &point[i].x, &point[i].y);
	} 
	qsort(point, n, sizeof(point[0]), cmp_px);
	printf("排序后:\n");
	for(i=0;i<n;i++)
	{
		printf("%lf %lf\n",point[i].x,point[i].y);
	}
	printf("最短距离为:%.2lf\n", MinDistance(0, n - 1));
 	return 0;
}

输入原始数据

P1234
X0.52-21
Y234-1

运行结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值