題目敘述
給定二維平面上 $n$ 個點,每一點都有座標 $(x_i,y_i)$ ,求出最近的點對之歐幾里德距離為多少?
$dis(p_i,p_j) = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$
平面最近點對有好多種實作方式,從最差的暴力枚舉、稍微優化的掃描線演算法、到分治與隨機,有4種不同的時間複雜度。利用TIOJ 1500這一題最近點對的裸題,來實測各種不同複雜度下所需要的執行時間。
暴力枚舉
時間複雜度:$O(N^2)$
Submission
時間:TLE,10440
暴力$O(n^2)$將所有點進行枚舉,因為值域是 $n≤50000$ ,平方枚舉會有TLE的問題。
程式碼
1 |
|
掃描線算法
時間複雜度:Worst Case $O(N^2)$
Submission
時間:AC,1668
這一種作法是改善過後的暴力枚舉,利用計算幾何中掃描線的概念,先將所有點依照x座標進行排序(y座標隨意)。接著想像一條從左往右掃的掃描線,對於每一條掃描線看右邊的點,如果當前最近點對距離為 $d$,因此只要遇上x座標差距大於 $d$ 的點時,即可繼續下一輪的枚舉。
加上排序的關係,其時間複雜度至少為 $O(n\log n)$,但這種掃描線的方式無法有效過濾所有點都在相同的x座標上的情況,因此最差的時間複雜度會退化成 $O(n^2)$ ,不過聽說平均的狀況下是很快的!
上圖為掃描線執行最近點對的一個示意圖,黑線為掃描線,$d$ 為掃描線左邊所有點的最近點對距離,我們只要每一輪枚舉這個點與右邊x座標差在 $d$ 以內的所有點,即可進行下一輪的更新!
程式碼
1 |
|
掃描線算法(優化後)
時間複雜度:$O(N\log N)$
Submission
時間:AC,148
原本以為上面的掃描線就是他的極限了,沒想到上面的worst case還可以透過set優化成 $O(n\log n)$!簡單來說,方法一樣是想像一條掃描線由左而右,一樣照上面的想法,把x座標差大於d的點排除,之後利用set二分搜找出y座標在範圍內的點進行枚舉更新答案。
- 將點輸入並且排序,X座標為主,Y座標為輔。
- 使用set,並以Y座標為排序基準(pair的首項),以儲存第 $i$ 點的左方、水平距離小於等於d的點。
- 右掃描線依序窮舉各點作為右端點。
(1) Erase與右端點水平距離大於d的點們(左掃描線右移)
(2) 用二分搜找出與第 $i$ 點垂直距離小於d的點,並嘗試更新
(3) 將第 $i$ 點加入set中。
程式碼
1 |
|
分治算法
時間複雜度:$O(N\log N)$
Submission
時間:AC,196
分治做最近點對的基本想法,先將所有點依照x座標排序,利用遞迴得到分割點左右兩邊所有點的最短距離(兩點並不會跨過中間分隔線),枚舉所有會橫跨兩側且有可能更新最短距離的點對。
從兩半邊的遞迴得到目前的最近點對距離 $d = min(d_l,d_r)$ ,將分隔線附近x座標差距小於$d$的點通通都枚舉一遍。可能會有一個疑問,我們是不是可以縮小枚舉的範圍,否則點的數量可能會太多導致複雜度爆炸?除了x座標可以做點的篩選之外,在枚舉的過程中,我們會利用將所有點對y座標排序,將y座標直線距離大於 $d$ 的情況剔除,所剩下真的需要枚舉點也只會剩下常數個,因此可以放心枚舉。
複雜度分析: 腦海中想像遞迴樹的長相,會發現每一層都需要都需要對y座標進行排序,時間為$O(n\log n)$ ,每一次都將n的值除以2,因此共有$O(\log n)$ 層,總共的時間複雜度為 $O(n\log^2n)$。(不過實際上應該會比這個快,因為並不是要對所有點都進行排序)。
如果要做得更快,可以在y座標排序的地方稍微動動手腳。既然每一層都要對y座標進行排序,排序好的東西再排序一次其實沒有什麼意義,因此就可以用)合併排序(merge sort)的方式,將所有已經排序好的兩個左右序列進行$O(n)$的合併(可以用std::merge()完成),如此一來,就不須要每一層花到 $O(n\log n)$ 的ㄕˊ間進行排序,使總複雜度降低為 $O(n\log n)$!
程式碼
1 |
|
隨機算法
時間複雜度:期望 $O(N)$
Submission
時間:AC,488
用隨機算法做最近點對的期望複雜度是 $O(n)$ ,也就是說如果一開始進行的Random_shuffle有做好的話,期望可以在線性時間解決這個問題。基本的想法如下:
- 將最近點對距離設為d,初始為第一、二個點之間的距離
- 將每一個點的座標塞入以 $\frac{d}{2}$ 為邊長的網格中
- 將點加入網格中,查看要加入的網格是否已經有點在其中
- 一個網格不可容納兩個點,否則必須更新最近點對的距離
- 在更新最近點對距離之後,將前面的點的網格座標以新的$d$進行更新
這個算法用到隨機的因子,因此如果在一開始有將所有點進行均勻的打散的話,可以做到期望複雜度 $O(n)$。
複雜度分析:
考慮加入第i+1個點時出現新的最近點對,發生的機率為:在$C_2^{i+1}$個配對中跟i+1個點產生最近點對共有i種可能因此機率為$\frac{2}{i+1}$。
當機率發生的時候,必須將所有的點都刪掉重新來一遍(r變小,重新推入i+1個點),需要付出$O(i+1)$的時間,相乘起來加入每一個點期望的複雜度為$O(1)$,因此總時間複雜度為$O(n)$。
程式碼
1 |
|