UVa Problem 10245 The Closest Pair Problem (最近点对问题)

// The Closest Pair Problem (最近点对问题)
// PC/UVa IDs: 111402/10245, Popularity: A, Success rate: low Level: 2
// Verdict: Accepted
// Submission Date: 2011-11-09
// UVa Run Time: 0.240s
//
// 版权所有(C)2011,邱秋。metaphysis # yeah dot net
//
// [解题方法]
// 经典问题,可以使用运行时间为 O(nlgn) 的分治算法,具体可参考 Thomas H. Cormen 《算法导论》
// 第 2 版第 33 章第 4 小节的内容。虽然算法是比较容易理解的,但是自己实际实现起来还是费了一番功
// 夫的,不过在自己实现了之后,对其理解又加深了一步,不断的练习实践,我想这是提高能力的唯一捷径。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <cmath>

using namespace std;

#define MAXN 10000		// 点的最大数量。
#define MAXDISTANCE (1E10)	// “无限大” 距离值,需要根据具体应用设置。

struct point
{
	double x;
	double y;
};

point points[MAXN];	// 记录点的坐标数据。
int pointsNumber;	// 点的总个数。

// 计算两点的距离,只是计算其距离的平方和。
inline double calDistance(point a, point b)
{
	return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}

// 分治法求最近点对距离。
double closestDistance(int P[], int Pn, int X[], int Xn, int Y[], int Yn)
{
	// 递归调用的出口,当拆分后点数小于等于 3 个时,使用穷举法计算最近距离。注意初始距离应
	// 设为 “无限大”,“无限大” 的具体值应该根据具体应用设置。
	if (Pn <= 3)
	{
		double distance = MAXDISTANCE;
		for (int i = 0; i < Pn - 1; i++)
			for (int j = i + 1; j < Pn; j++)
			{
				double tmp = calDistance(points[P[i]], points[P[j]]);
				distance = min(distance, tmp);
			}
			
		return distance;
	}
	
	// 分解:把点集 P 划分为两个集合 Pl 和 Pr。并得到相应的 Xl,Xr,Yl,Yr。
	int Pl[MAXN], Pln, Pr[MAXN], Prn;
	int Xl[MAXN], Xln, Xr[MAXN], Xrn;
	int Yl[MAXN], Yln, Yr[MAXN], Yrn;

	// 标记某点是否在划分的集合 Pl 中。初始时,所有点不在集合 Pl 中。
	bool inPl[MAXN];
	memset(inPl, false, sizeof(inPl));

	// 将数组 P 划分为两个数量接近的集合 Pl 和 Pr。Pl 中的所有点在线 l 上或在 l 的左侧,
	// Pr 中的所有点在线 l 上或在 l 的右侧。数组 X 被划分为两个数组 Xl 和 Xr,分别包含
	// Pl 和 Pr 中的点,并按 x 坐标单调递增的顺序排序。类似的,数组 Y 被划分为两个数组 Yl
	// 和 Yr,分别包含 Pl 和 Pr 中的点,并按 y 坐标单调递增的顺序进行排序。对于 Xl,Xr,
	// Yl, Yr,由于参数 X 和 Y 均已排序,只需从中拆分出相应的点即可,并不需要再次排序,
	// 拆分后的仍保持排序的性质不变,这是获得 O(nlgn) 运行时间的关键,否则若再次排序,运
	// 形时间将为 O(n(lgn)^2)。
	int middle = Pn / 2;
	Pln = Xln = middle;
	for (int i = 0; i < Pln; i++)
	{
		Pl[i] = Xl[i] = X[i];
		inPl[X[i]] = true;
	}
	Prn = Xrn = (Pn - middle);
	for (int i = 0; i < Prn; i++)
		Pr[i] = Xr[i] = X[i + middle];

	// 根据某点所属集合,划分 Yl 和 Yr。
	Yln = Yrn = 0;
	for (int i = 0; i < Yn; i++)
		if (inPl[Y[i]])
			Yl[Yln++] = Y[i];
		else
			Yr[Yrn++] = Y[i];

	// 解决:把 P 划分为 Pl 和 Pr 后,再进行两次递归调用,一次找出 Pl 中的最近点对,另一次
	// 找出 Pr 中的最近点对。
	double distanceL = closestDistance(Pl, Pln, Xl, Xln, Yl, Yln);
	double distanceR = closestDistance(Pr, Prn, Xr, Xrn, Yr, Yrn);

	// 合并:最近点对要么是某次递归调用找出的距离为 distance 的点对,要么是 Pl 中的一个点
	// 与 Pr 中的一个点组成的点对,算法确定是否存在其距离小于 distance 的一个点对。
	double minDistance = min(distanceL, distanceR);
	

	// 建立一个数组 Y‘,它是把数组 Y 中所有不在宽度为 2 * minDistance 的垂直带形区域内
	// 的点去掉后所得的数组。数组 Y’ 与 Y 一样,是按 y 坐标顺序排序的。
	int tmpY[MAXN], tmpYn = 0;
	for (int i = 0; i < Yn; i++)
		if (fabs(points[Y[i]].x - points[X[middle]].x) <= minDistance)
			tmpY[tmpYn++] = Y[i];

	// 对数组 Y‘ 中的每个点 p,算法试图找出 Y’ 中距离 p 在 minDistance 单位以内的点。仅
	// 需要考虑在 Y‘ 中紧随 p 后的 7 个点。算法计算出从 p 到这 7 个点的距离,并记录下 Y‘
	// 的所有点对中,最近点对的距离 tmpDistance。
	double tmpDistance = MAXDISTANCE;
	for (int i = 0; i < tmpYn; i++)
	{
		int top = ((i + 7) < tmpYn ? (i + 7) : (tmpYn - 1));
		for (int j = i + 1; j <= top; j++)
		{
			double tmp = calDistance(points[tmpY[i]], points[tmpY[j]]);
			tmpDistance = min(tmpDistance, tmp);
		}	
	}

	// 如果 tmpDistance 小于 minDistance,则垂直带形区域内,的确包含比根据递归调用所找
	// 出的最近距离更近的点对,于是返回该点对及其距离 tmpDistance。否则,就返回递归调用中
	// 发现的最近点对及其距离 minDistance。
	return min(minDistance, tmpDistance);
} 

bool cmpX(int a, int b)
{
	return points[a].x < points[b].x;
}

bool cmpY(int a, int b)
{
	return points[a].y < points[b].y;
}

double calClosestDistance()
{
	// 准备初始条件,注意,在本解决方案中,数组中保存的只是各个点的序号而已,并不是点的坐标,
	// 这样可以减少一些数据复制的时间,同时不影响算法的实现。
	int P[MAXN], Pn;
	int X[MAXN], Xn;
	int Y[MAXN], Yn;

	// 赋初值。
	Pn = Xn = Yn = pointsNumber;
	for (int i = 0; i < pointsNumber; i++)
		P[i] = X[i] = Y[i] = i;

	// 预排序,按 x 坐标和 y 坐标分别排序。
	sort(X, X + Xn, cmpX);
	sort(Y, Y + Yn, cmpY);

	// 调用分治算法。
	return closestDistance(P, Pn, X, Xn, Y, Yn);
}

int main(int ac, char *av[])
{
	cout.precision(4);
	cout.setf(ios::fixed | ios::showpoint);

	while (cin >> pointsNumber, pointsNumber)
	{
		for (int i = 0; i < pointsNumber; i++)
			cin >> points[i].x >> points[i].y;
		
		double minDistance = sqrt(calClosestDistance());
		
		if (minDistance > 10000.0)
			cout << "INFINITY" << endl;
		else
			cout << minDistance << endl;	
	}

	return 0;
}


你可能感兴趣的:(UVa Problem 10245 The Closest Pair Problem (最近点对问题))