// 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];elseYr[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;elsecout << minDistance << endl;}return 0;}