題意
給出若干個點(不大于100000個),求出這些點中距離最小的兩個點的距離的一半(最近點對問題)
解析
暴力解法是此類問題的一種解法,不過在這里肯定就超時了。正確的解法應該是分治算法,網上應該有很多解析。這里附上一些題解,還有關于最近點對問題的分析,下面是我對于此題AC的代碼
AC代碼
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
#define MAX 2147483647
using namespace std;
struct Point{
double x,y;
}q[100010];
int dp[100010],n;
bool cmp(Point a,Point b){ //最開始的排序,x和y軸都從小到大
if(a.x != b.x) return a.x < b.x;
return a.y < b.y;
}
double dis(int a,int b){ //計算兩點之間的距離
return sqrt((q[a].x-q[b].x)*(q[a].x-q[b].x) + (q[a].y-q[b].y)*(q[a].y-q[b].y));
}
bool cmp2(int a,int b){ //一個重要的排序
return q[a].y < q[b].y;
}
double getClosePair(int left,int right){
if(left == right) return MAX; //這個區域只有一個點
if(left + 1 == right) return dis(left,right); //如果只有兩個點,直接返回距離
int mid = (left + right)/2;
double t1 = getClosePair(left, mid); //遞歸求距離
double t2 = getClosePair(mid + 1, right);
double t = t1 < t2 ? t1 : t2;
int temp = 0;
for(int i = left; i <= right; i++) //篩選x軸距離小于t的
if(fabs(q[mid].x - q[i].x) <= t)
dp[temp++] = i;
sort(dp, dp + temp, cmp2); //一個重要的排序
for(int i = 0; i < temp; i++)
for(int j = i+1; j < temp && q[dp[j]].y - q[dp[i]].y < t; j++){ //篩選y軸距離小于t的
double t3 = dis(dp[i],dp[j]);
if(t3 < t) t = t3; //更新最小值
}
return t;
}
int main(){
while(cin>>n,n){
for(int i=0; i<n; i++)
cin>>q[i].x>>q[i].y;
sort(q,q+n,cmp);
cout<<fixed<<setprecision(2)<<getClosePair(0, n-1)/2<<endl;
}
}
換個思維
- 代碼我相信網上多的是,我這份也不過是參考最原始的改編的而已。但是我現在有一個問題。上面的
getClosePair
函數能不能改成下面這個樣子
double getClosePair(int left,int right){
......
for(int i = left; i <= right; i++)
if(fabs(q[mid].x - q[i].x) <= t)
dp[temp++] = i;
//sort(dp, dp + temp, cmp2); 注意這個排序不要了
for(int i = 0; i < temp; i++)
for(int j = i+1; j < temp && fabs(q[dp[j]].y - q[dp[i]].y) < t; j++){ //注意這里的判斷加上了fabs
double t3 = dis(dp[i],dp[j]);
if(t3 < t) t = t3;
}
return t;
}
- 為什么想要這樣改?上面的改動是這樣的,去掉了對dp數組的排序。用
fabs
來控制q[dp[j]].y - q[dp[i]].y
的符號。我們現在返回去看為什么要有sort(dp, dp + temp, cmp2);
,這個排序,是將這塊區域中的所有點按y值從小到大排序。因為j=i+1
,所以j>i
,所以q[dp[j]].y - q[dp[i]].y
肯定是正數。難道說這個排序只是為了達到可以保持正數的效果? - 抱著這個只是為了保證正數的想法,我去掉了
sort
,加上了fabs
,然后光榮WA
了。
為什么?
每個程序都有它的核心代碼,理解了核心代碼就可以說理解了整個程序。而這個sort
我覺得就是里面最妙的一筆。我用兩段代碼來解釋為什么fabs
不行
代碼一:
for(int i = 0; i < temp; i++)
for(int j = i+1; j < temp && fabs(q[dp[j]].y - q[dp[i]].y) < t; j++){
double t3 = dis(dp[i],dp[j]);
if(t3 < t) t = t3;
}
代碼二:
for(int i = 0; i < temp; i++)
for(int j = i+1; j < temp; j++){
if(fabs(q[dp[j]].y - q[dp[i]].y) < t){
double t3 = dis(dp[i],dp[j]);
if(t3 < t) t = t3;
}
}
這兩段代碼的區別就在于這個fabs
的判斷是放在for
里面還是if
里面。首先我告訴你們,代碼二的換到上面的AC代碼中,它的結果是對的,但是時間超了;代碼一換到上面的AC代碼中,它的結果是不對的。
區別我相信很多人能看出來,在for
中判斷,如果不滿足條件,直接退出內循環,外循環的i++
。如果是在for
里面的if
判斷,則不滿足條件后還會繼續外循環。也就是是否退出外循環的區別。
真相只有一個
在用分治法求最小距離時,我們把區域一塊一塊的分開,當把兩塊合并的時候,最小的距離是
左邊那塊最小的距離
,右邊那塊最小的距離
,中間部分的最小距離
這三段中取最小。
左邊和右邊直接可以得到。在計算中間部分時我們首先求得左邊那塊和右邊那塊較小的距離t
,然后在合并后的區域中,找到離合并中點的x軸距離小于t
的所有的點。
for(int i = left; i <= right; i++) //篩選x軸距離小于t的
if(fabs(q[mid].x - q[i].x) <= t) //mid表示中心位置
dp[temp++] = i;
從上述得到的點中,再找出所有y軸x相對距離小于t
的兩點,然后這兩點的距離,判斷是否還小于t
。換句話說,就是如果兩個點的x軸
相對距離和y軸
相對距離都小于目前的最小距離t
。則計算這兩點的實際距離,判斷是否小于t
,如果小于則刷新t
。這個過程中省去了大量的距離大的點的計算。代碼如下
sort(dp, dp + temp, cmp2); //一個重要的排序
for(int i = 0; i < temp; i++)
for(int j = i+1; j < temp && q[dp[j]].y - q[dp[i]].y < t; j++){ //篩選y軸距離小于t的
double t3 = dis(dp[i],dp[j]);
if(t3 < t) t = t3; //更新最小值
}
剛才說,把fabs
放在for
里面的if
里面也可以,但是會超時。而這就是這個sort
的第二個用處。
超時主要是在內層循環中,而能及時跳出內層循環,達到剪支效果的,毫無疑問就是根據y軸
排序,這樣當某一點不再滿足小于時,后續的點肯定也不滿足。
現在想想好像是很簡單……用一個 sort
搞定了剪支而已。但是,我的看法就是,這個分治,最重要的地方就在于合并的最小距離處理(就像剛才,如果用fabs就是超時)。而理解了這個sort
,其實也就理解了整個程序(個人看法)