两种方法对最近对问题的解释
背景描述:
终于,隔了将近一周,开始更新第二篇算法博客。今天的问题是最近对问题。问题描述如下:对于二维坐标系中的若干个点,从中判断出相距最近的两个点,并输出最近的这个距离。
我之前也翻看了很多篇博客,大家都解释的很好,但是都只是输出了最近的距离,而我希望这个程序不仅能输出最近距离,同时还能锁定是哪两个点得到的这个最近距离。于是,经过反复试错后,终于得到满意的结果,马不停蹄过来更新博客了。
蛮力法:
首先介绍蛮力法的思路。顾名思义,就是从第一个点开始,求它和第二个点之间的距离,然后求它和第三个点之间的距离,依次往后遍历。经过两重循环之后就可以得出最短的距离,同时锁定是哪两个点产生的。
可以设置两个一维数组,一个存储横坐标,另一个存储纵坐标,注意要两个数组彼此之间一一对应。完整算法如下(由于蛮力法较为简单,此处只给出算法部分,完整程序请参考下面分治法):
//返回最短长度,并得到该两个点在数组中的下标索引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函数动态分配内存空间的,如果报错,原因极可能是代码出错。
最后的最后,这几天莫名的有些焦虑。因为挑战杯,因为大学生计算机设计大赛,很多比赛摆在面前但是我却不知道怎么参加,不知道和谁一起参加,不知道自己的能力够不够参加,但是内心对于参赛这种经历的渴望又在催促我去参加。自我的怀疑和对未知的渴望在我心中纠缠,好焦虑。唉,明天还是去找老师聊一聊伐。