// 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; }