分治算法——最邻近点对
设计与实现查找平面上的最邻近点对问题的算法。
解决思路
如果说用暴力的方法来解决这道题,我们需要将所有的点两两进行比较,两层循环的时间复杂度为O(n),那么如何降低时间复杂度呢?如果空间中只存在一个点,那么就不存在最近点对,如果空间中只有两个点时,最近点对就是这两个点,那么当空间中有三个点时,我们便可以将空间划分为两个部分,左半部分有两个点,右半部分有一个点,那么我们就可以将问题划分为求左半部分,右半部分以及交界处的最近点对的最小值。可是当点越来越多的时候,我们应该如何求得两个子控件交界处的最近点对呢?我们首先需要将随机生成的点对进行排序,之后我们才可以讨论如何对空间进行划分,假设我们用x=mid将空间划分为了两个部分,并且我们已经求得了左半部分最小点对间的距离为d1,右半部分最近点对间的距离,我们还需要找到交界处的最近点对。我们刚刚得到了d1与d2,如果我们不需要考虑交界处的点,那么最近点对间的距离就是dmin = min(d1,d2),我们可以利用这个dmin来划分一下交界处的范围,即对于任意一个点(x,y),当fabs(x-min)<=dmin时,这个点才属于交界处,才在我们需要考虑的范围内,如果当fabs(x-min)>dmin,那么该点与另一空间中的点的距离一定大于dmin。划定范围后我们就可以用暴力的方法找到交界处的最近点对,并与dmin进行比较得到最近点对。通过递归的方法我们就可以得到整个数组的最近点对
算法代码
struct Point
{
int x, y;
bool operator< (const Point& p)const
{
return x < p.x || (x == p.x && y < p.y);
}
bool operator== (const Point& p)const
{
return x == p.x && y == p.y;
}
};
struct Pair
{
int flag1, flag2;
};
double distance(Point p1, Point p2)
{
return sqrt(pow(p1.y - p2.y,2) + pow(p1.x - p2.x,2));
}
double CloestPair(Point points[], int length)
{
sort(points, points + length);
return Cloest(points, 0, length - 1);
}
//仅返回最近点对的距离的代码
double Cloest(Point points[], int start, int end)//仅返回最近点对间距离的算法
{
if (start == end)//如果空间中只有一个点,则返回double类型的最大值
return DBL_MAX;
if (end - start == 1)
return distance(points[start], points[end]);
int mid = (start + end) / 2;
double left = Cloest(points, start, mid);//求出左半部分的最近点对间的距离
double right = Cloest(points, mid + 1, end);//求出右半部分的最近点对间的距离
double mindis = min(left, right);
Point *temp = new Point[end - start + 1];//用于记录处于交界范围内的点
int k = 0;
for (int i = start; i <= end; i++)//找到处于交界范围的点
if (fabs(points[mid].x - points[i].x) <= mindis)
temp[k++] = points[i];
for (int i = 0; i < k; i++)//计算交接范围内的最近点对间的距离
for (int j = i + 1; j < k; j++)
{
if (temp[j].y - temp[i].y > mindis)continue;
mindis = distance(temp[i], temp[j]) < mindis ? distance(temp[i], temp[j]) : mindis;
}
delete[] temp;
return mindis;
}
//返回最近点对的代码
Pair Cloest(Point points[], int start, int end,int length)//返回最近点对下标的算法
{
Pair res = { 0, length-1 };
//将res的默认值设置为0、len-1,因为数组是经过排序的,所以这样能保证默认的点对间聚利时最大的
if (start == end)
return res;//如果空间中只有一个点,那么就返回数组中距离最大的点对
if (end - start == 1)
{
res.flag1 = start, res.flag2 = end;
return res;
}
int mid = (start + end) / 2;
double dis;
Point t = { -1,-1 };//用来处理在比较过程中被抛弃的点的异常值
Pair left = Cloest(points, start, mid, length);//左半边的最近点对
Pair right = Cloest(points, mid + 1, end, length);//右半边的最近点对
//得到左右两部分的最近点对
if (distance(points[left.flag1], points[left.flag2]) <= distance(points[right.flag1], points[right.flag2]))
res = left, dis = distance(points[left.flag1], points[left.flag2]);
else
res = right, dis = distance(points[right.flag1], points[right.flag2]);
Point *temp = new Point[end - start + 1];//用于记录属于交界处范围的点
int k = 0;
//扫描整个数组,找出属于交界处范围内的点
for (int i = start; i <= end; i++, k++)
//如果该点与中间点之间的横坐标之差小于等于最小距离,则记录下该点,否则抛弃该点,用异常值代替
if (fabs(points[mid].x - points[i].x) <= dis)
temp[k] = points[i];
else
temp[k] = t;
for (int i = 0; i < k; i++)
for (int j = i + 1; j < k; j++)
{
//如果两点之间的纵坐标之差大于最小距离,或任意一个点为异常值,则进入下一次循环
if (temp[j].y - temp[i].y > dis || temp[i] == t || temp[j] == t)continue;
int tmpdis = distance(temp[i], temp[j]);
if (tmpdis < dis)//如果两点距离小于最小距离,记录下这两个点
{
dis = tmpdis;
res.flag1 = i + start;
res.flag2 = j + start;
//由于这两个点存放在新的数组即temp中,所有我们最终需要返回他们在原数组中的真实位置
}
}
delete[] temp;
return res;
}