蛮力法-分治法-处理最近对问题

两种方法对最近对问题的解释

背景描述:

  终于,隔了将近一周,开始更新第二篇算法博客。今天的问题是最近对问题。问题描述如下:对于二维坐标系中的若干个点,从中判断出相距最近的两个点,并输出最近的这个距离。
  我之前也翻看了很多篇博客,大家都解释的很好,但是都只是输出了最近的距离,而我希望这个程序不仅能输出最近距离,同时还能锁定是哪两个点得到的这个最近距离。于是,经过反复试错后,终于得到满意的结果,马不停蹄过来更新博客了。

蛮力法:

  首先介绍蛮力法的思路。顾名思义,就是从第一个点开始,求它和第二个点之间的距离,然后求它和第三个点之间的距离,依次往后遍历。经过两重循环之后就可以得出最短的距离,同时锁定是哪两个点产生的。
  可以设置两个一维数组,一个存储横坐标,另一个存储纵坐标,注意要两个数组彼此之间一一对应。完整算法如下(由于蛮力法较为简单,此处只给出算法部分,完整程序请参考下面分治法):

//返回最短长度,并得到该两个点在数组中的下标索引index1和index2
float Getclosedpoint(int *x, int *y, int n, int &index1, int &index2)
{
	float min = (x[0] - x[1])*(x[0] - x[1]) + (y[0] - y[1])*(y[0] - y[1]);
	float path;
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = i + 1; j < n; j++)
		{
			path = (x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])*(y[i] - y[j]);
			if (path <= min)
			{
				min = path;
				index1 = i;
				index2 = j;
			}
		}
	}
	return min;
}

蛮力法时间复杂度分析:

  假设有n个点,则从外层循环是从第一个数字开始,循环到第n-1个数停止,共循环n-1次。内层循环一开始从第一个数开始,遍历求与后面所有的点之间的距离,共n-1次,最后一次循环从第n-1个数开始,求和第n个数之间的距离,执行1次。所以共执行1+2+…+(n-2)+(n-1)=n(n-1)/2;所以时间复杂度O(n2).

分治法

  分治法顾名思义是分而治之,将一个大问题分解成几个小问题再逐个解决。对于最近对问题,分治法的思路如下:
  1.首先将这些杂乱的点按照横坐标进行排序,然后取中间值mid将这堆点分成左右两部分。
  2.分别对左边和右边的两小堆点集再次递归使用分治法,进而得到对应的最短距离d1和d2。
  3.接下来处理mid中间线部分的情况。因为有一种可能就是距离最短的两个点一个在左区域一个在右区域。因此我们要对这种情况进行解决。办法是从刚才第二步中的两个结果中取最小值d,然后以步骤1中的中间值mid为基准,划分出宽度为2d的一个区域,然后将这个区域中的点进行蛮力法求得对应的最短距离d3。
  4.最后将结果进行比较。
  算法的完整程序如下:

#include<iostream>
#include<math.h>
#include<time.h>
using namespace std;

struct point {
	int x, y;
};

//对数组R[low]到R[hjgh]之间进行一次划分,返回划分之后中枢轴的位置
int Partition(point *R, int low, int high)
{
	int pivotkey;
	R[0] = R[low];
	pivotkey = R[low].x;
	while (low < high)
	{
		while (low < high&&R[high].x >= pivotkey)
			high--;
		if (low < high)
			R[low++] = R[high];
		while (low < high&&R[low].x <= pivotkey)
			low++;
		if (low < high)
			R[high--] = R[low];
	}
	R[low] = R[0];
	return low;
}
//对记录序列R[s..t]进行快速排序
void Sort(point *R, int s, int t)
{
	int pivotloc;
	if (s < t)
	{
		pivotloc = Partition(R, s, t);
		Sort(R, s, pivotloc - 1);
		Sort(R, pivotloc + 1, t);
	}
}
//对数组R[]进行快速排序 R[0]为哨兵单元
void QuickSort(point *R, int n)
{
	Sort(R, 1, n - 1);
}

double Distance(point a, point b)
{
	return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}

int index(double x, point *S, int low, int high)
{
	for (int i = 0; i < high - low + 1; i++)
	{
		if (S[i].x <= x && S[i + 1].x > x)
			return i;
	}
}

//对点坐标数组S[low]和S[high]之间的元素进行递归查找,返回其中最近的两个点之间的距离
//其中loc1,loc2表示这两个点在S[]中的下标
double Closest(point S[], int low, int high,int &loc1,int &loc2)
{
	double d1, d2, d3, d;
	int a1, b1, a2, b2;  //用于递归时记录最近对点的坐标
	int mid;
	int n1 = 0;   //递归函数中,用来表示最近对的下标
	int n2 = 0;
	if (high - low == 1)   //只有两个点
	{
		loc1 = low;
		loc2 = high;
		return Distance(S[low], S[high]);
	}
	if (high - low == 2)   //只有三个点,求最近距离
	{
		d1 = Distance(S[low], S[low + 1]);
		d2 = Distance(S[low + 1], S[high]);
		d3 = Distance(S[low], S[high]);
		if ((d1 < d2) && (d1 < d3))
		{
			loc1 = low;
			loc2 = low + 1;
			return d1;
		}
		else if (d2 < d3)
		{
			loc1 = low + 1;
			loc2 = high;
			return d2;
		}
		else
		{
			loc1 = low;
			loc2 = high;
			return d3;
		}
	}
	mid = (low + high) / 2;     //计算中间点
	d1 = Closest(S, low, mid, a1, a2);   //递归求解子问题1
	d2 = Closest(S, mid + 1, high, b1, b2);   //递归解决子问题2
	if (d1 < d2)                      //以下求解子问题3
	{
		d = d1;
		loc1 = a1;
		loc2 = a2;
	}
	else
	{
		d = d2;
		loc1 = b1;
		loc2 = b2;
	}
	//筛选范围 S[mid].x-d, S[mid].x+d
	int index1 = 0;
	int index2 = 0;
	for (int i = 0; i < high - low + 1; i++)
	{
		if (S[i].x <= S[mid].x - d && S[i + 1].x > S[mid].x - d)
		{
			index1 = i;
			break;
		}
	}
	for (int i = 0; i < high - low + 1; i++)
	{
		if (S[i].x <= S[mid].x + d && S[i + 1].x > S[mid].x + d)
		{
			index2 = i;
			break;
		}
	}
	//对S[index1]和S[index2]之间进行蛮力法
	for (int i = index1; i < index2 - index1; i++)
	{
		for (int j = i + 1; j < index2 - index1; j++)
		{
			if (S[j].y - S[i].y >= 0)
				break;
			else
			{
				d3 = Distance(S[i], S[j]);
				if (d3 < d)
				{
					d = d3;
					loc1 = i;
					loc2 = j;
				}
			}
		}
	}
	return d;
}

int main()
{
	point *S = new point[11];
	int x[10] = { 12,14,4,77,89,9,99,96,77,93 };
	int y[10] = { 13,56,55,67,34,21,65,78,87,22 };
	S[0].x = 0;
	S[0].y = 0;
	for (int i = 1; i < 11; i++)
	{
		S[i].x = x[i - 1];
		S[i].y = y[i - 1];
	}
	QuickSort(S, 11);
	int index1 = 0;
	int index2 = 0;
	cout << Closest(S, 1, 10, index1, index2) << endl;
	//cout << index1 << " " << index2 << endl;
	cout << "(" << S[index1].x << "," << S[index1].y << ") 和 (" << S[index2].x << "," << S[index2].y << ")" << endl;
	cout << "The run time is:" << (double)clock() / CLOCKS_PER_SEC << "s" << endl;
}

分治法的时间复杂度分析

  对于分治法,假设有k个数,当k=2时,时间复杂度为:T(n)=1;
当k>2时,时间复杂度为T(n)=2T(n/2)+n;对这样的递推式最终得到T(n)=O(nlog2n )。

算法总结

  对于分治法,有三点总结:1.写递归函数时,一定一定一定要考虑到递归跳出的情况,写出这种情况时如何return。2.对于确定点的坐标,递归函数中将index作为引用参数,传入函数体内,这种思路没毛病,需要注意的是,在函数体内再次调用递归函数时,传入的引用不能再是index,要用新的变量。否则的话index就得不到想要的值。3.对于递归函数,也是可以用new函数动态分配内存空间的,如果报错,原因极可能是代码出错。
  最后的最后,这几天莫名的有些焦虑。因为挑战杯,因为大学生计算机设计大赛,很多比赛摆在面前但是我却不知道怎么参加,不知道和谁一起参加,不知道自己的能力够不够参加,但是内心对于参赛这种经历的渴望又在催促我去参加。自我的怀疑和对未知的渴望在我心中纠缠,好焦虑。唉,明天还是去找老师聊一聊伐。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值