【最近點】詳解4種不同複雜度之算法 |詳解平面點集最近點對問題 |平面最近點對 |

【最近點】詳解4種不同複雜度之算法 |詳解平面點集最近點對問題 |平面最近點對 |

下面我們介紹一種時間複雜度 分治算法來解決這個問題。

該算法 1975 年 Franco P. Preparata 提出,Preparata 和 Michael Ian Shamos 證明瞭該算法決策樹模型下是優的。

分治算法一樣,我們這個有 個點集合拆分成兩個大小集合 ,並遞歸下去。

但是我們遇到了一個難題:如何合併?即如何求出一個點 中,另一個點 中最近點?這裏我們設合併操作時間複雜度 ,可知算法總複雜度 。

我們所有點 為第一關鍵字、 為第二關鍵字排序,並以點 為分界點,拆分點集為 :並遞歸下去,求出兩點集各自內部最近點,設距離為 ,取較小值設為 。

現在該合併了!我們試圖找到這樣一組點,其中一個屬於 ,另一個屬於 ,且二者距離於 。

因此我們所有橫座標 於 點放入集合 :結合圖像,直線 將點分成了兩部分。

左側 點集,右側 點集。

規則,得到綠色點組成 點集。

於 中每個點 ,我們當前目標是找到一個 中、且到其距離於 點。

避免兩個點之間考慮,我們只考慮那些縱座標於 點。

顯然於一個合法點 , 於 。

於是我們獲得了一個集合 :點集 中選一點 , 規則,得到了紅色方框內黃色點組成 點集。

如果我們 中點 排序, 得到,鄰 幾個點。

注意到我們上文提到了兩次排序,因為點座標全程不變,第一次排序可以只在分治開始前進行一次。

我們令每次遞歸返回當前點集 排序結果,於第二次排序,上層直接使用下層兩個排序過點集歸併。

這個算法, 處於 數量級,導致總複雜度。

其實不然,其大小為 ,我們給出它證明:我們瞭解到, 中所有點縱座標 範圍內;且 中所有點,和 本身,橫座標 範圍內。

這構成了一個 矩形。

我們這個矩形拆分兩個 正方形,考慮 ,其中一個正方形中點為 ,另一個 ,且兩個正方形內任意兩點間距離於 。

(因為它們來一下層遞歸)我們一個 正方形拆分四個 小正方形。

平面上圖形,做360°旋轉;兩條垂直線,左右夾緊圖形,找到極端頂點。

最近点 Play

由此,每個正方形中有 個點,矩形中有 個點,去掉 本身,。

我們使用一個結構體來存儲點,並定義於排序函數對象:實現遞歸,我們引入 upd_ans() 輔助函數來計算兩點間距離並嘗試答案:下面是遞歸本身:假設調用前 a[] 排序。

如果 過,使用暴力算法計算 ,終止遞歸。

我們使用 std::inplace_merge() 來執行歸併排序,並創建輔助緩衝區 t[], 存儲其中。

題目敍述定二維平面上 $n$ 個點,每一點有座標 $(x_i,y_i)$ ,求出最近點歐幾裏德離多少?$dis(p_i,p_j) = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$平面最近點有好多種實作方式,從暴力枚舉、優化掃描線演算法、到分治,有4種時間複雜度。

利用TIOJ 1500這一題最近點對裸題,來實測各種複雜度下需要執行時間。

Submission時間:TLE,10440暴力$O(n^2)$將所有點進行枚舉,因為值域是 $n≤50000$ ,平方枚舉會有TLE問題。

Submission時間:AC,1668這一種作法是改善過後暴力枚舉,利用計算幾何中掃描線概念,所有點x座標進行排序(y座標)。

接著想像一條左往右掃掃描線,於每一條掃描線看右邊點,如果當前最近點距離 $d$,因此只要遇上x座標差距於 $d$ 點時,繼續下一輪枚舉。

加上排序關係,其時間複雜度為 $O(n\log n)$,但這種掃描線方式無法過濾所有點x座標上情況,因此時間複雜度會退化成 $O(n^2)$ ,不過聽説狀況下是!上圖掃描線執行最近點對一個示意圖,黑線掃描線,$d$ 掃描線左邊所有點最近點距離,我們只要每一輪枚舉這個點右邊x座標差 $d$ 以內所有點,進行下一輪!Submission時間:AC,148原本以為上面掃描線他了,沒想到上面worst case可以透過set優化成 $O(n\log n)$!來説,方法一樣是想像一條掃描線左而右,照上面想法,x座標差於d點排除,後利用set二分搜找出y座標範圍內點進行枚舉答案。

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)$。

(不過應該會這個,因為並不是要所有點進行排序)。

顯然,分和治部分實現,本文將討論“合”實現。

令 k=\min(k_1,k_2) ,因為我們目標是尋找是否邊界線周圍是否有距離於 k 點,所以我們邊界線左右分別劃分一個 k 區域,考慮這個範圍內點(下圖紅色區域)。

劃分完邊界附近區域後,我們開始考慮“合”,即尋找 k_1 和 k_2 距離兩個點。

想到是,通過蠻力算法,我們紅色區域裏每一點離全部計算出來,然後得到短距離。

但是這種方法時間複雜度是 O(n^2) ,並沒有實質上降低時間複雜度。

蠻力算法時間複雜度為什麼是 O(n^2) ?是因為我們每一個點紅色區域裏所有點計算距離,所以我們改進算法思路是,能不能讓每一個點個點計算距離,從而使時間複雜度降低到 O(n) ?現在我們考慮一個問題,於下圖中紅色區域,上述劃分可知,每個正方形區域中任意兩點之間距離於k,那麼兩個正方形區域中多可以容納多少點?如下圖所示,多只能容納8個點,8個點分佈兩個正方形四角。
最近点

説,沿縱軸長 k 劃分紅色區域內,有八個點,同時,因為我們目標是找到跨越邊界線、距離於 k 兩點,所以跨越邊界線兩點之間縱座標差值於於 k 。

基於這兩點原因,我們可以很地看到,一個點縱座標它相近7個點計算距離,沒有與所有點之間計算距離。

我們可以得到如下設計思路:「掃描線」是計算幾何領域基礎手法,可以用來解決許多計算幾何問題,有如圖論中BFSDFS一樣經典。

它並不是一個物品,而是一個概念。

一條(或兩條)無限長平行線,其垂直方向移動,畫面一端移動到另一端,只在頂點處停留。

實作時,是座標大小排序所有頂點,然後兩索引值,記錄平行線位置哪個頂點上面。

兩條平行線,一條主,穿越整個畫面;一條輔,跟著主線狀況進行平移。

這是陰陽道理。

後面章節Closest Pair,實際示範掃描線實作方式。

一條(或兩條)原點射出射線,做360°旋轉,只在頂點處停留。

實作時,是極角大小排序所有頂點,然後兩索引值,記錄射線位置哪個頂點上面。

兩條射線,一條主,轉過整個畫面;一條輔,跟著主線狀況進行平移。

這是陰陽道理。

説穿了,掃描線基本精神:排序、搜尋。

計算幾何當中,有一個特性是「區域性」。

比如説,距離相近點,總是羣聚在一起;距離點,總是距離相近點隔開。

排序目的,建立「區域性」,使得搜尋條件,搜尋速度。

觀察問題是否有「重疊」、「相交」、「間隔」、「相聚」之類性質,然後掃描線進行掃描。

或者換句話説,排序所有頂點,進行搜尋。

「旋轉卡尺」是計算幾何領域基礎手法,它並不是一個物品,而是一個概念。

平面上圖形,做360°旋轉;兩條垂直線,左右夾緊圖形,找到極端頂點。

切換視點,變成:兩條無限長平行線,做360°旋轉,嘗試夾緊平面上圖形,找到極端頂點。

實作時,是兩索引值,記錄平行線位置哪個頂點上面。

兩條平行線,一條主,轉過整個畫面;一條輔,跟著主線狀況進行鬆。

這是陰陽道理。

實作時,只需轉180°。

轉半圈,兩條平行線調,效果同360°。

後面章節Farthest Pair,實際示範旋轉卡尺實作方式。

説穿了,旋轉卡尺基本精神:舉斜率,判斷目標對象有多斜。

使用旋轉卡尺夾住圖形,卡尺夾地方,是該圖形凸包。

旋轉卡尺夾到頂點順序,凸包頂點順序。

因此,旋轉卡尺適合用於凸包、凸多邊形相關問題。

學過凸包讀者,請見本站文件「Convex Hull」。

一羣點中,距離最近兩個點叫作「最近點」。

舉法,時間複雜度O(N²),可以找出所有最近點。

實作時,減少sqrt函式呼叫次數,記錄距離平方,可以減少計算時間。

預先水平座標排序,水平距離裁減搜尋範圍。

情況是所有點同一條垂直線上,會舉到所有點,完全裁減搜尋範圍。

儘管時間複雜度是O(N²),但是實際效率。

我時間複雜度是多少。

實作時,避免直接排序原資料,複製一份指標或索引值來排序,可以減少計算時間。

要避免情況,有個想法是:所有點垂直方向排序。
最近点

如此,時間複雜度降低O(NlogN)。

一、位於右端點左方、距離d以下點,才有可能形成最近點。

右端點圓心、左半圓涵蓋點(包含邊界)。

照理來説,我們只需要檢查半圓範圍裡面點。

運用左掃描線作為輔助,隨右掃描線亦步亦趨,我們得以過濾出水平距離d以下點,但是進一步過濾出半徑距離d以下點。

折衷方式是Y座標排序水平距離d以下點,然後運用二元搜尋法找到右端點,然後找到Y座標右端點、點──這些點可能半圓之內。

二、右端點左方點,兩兩之間距離,是d。

點點之間無法過密集擁擠,能夠組成最近點對左端點並多。

其實只需檢查右端點前兩點、後兩點,作為左端點,能囊括所有最近點!端的情況,是右端點中心、左半正方形涵蓋點(包含邊界)。

於右掃描線右移動,水平距離d以下點不斷排序,花時間。

運用Binary Tree資料結構,保持排序,節省時間。

掃描線輔資料結構,是計算幾何經典手法,請讀者牢記心。

時間複雜度。

排序O(NlogN)。

掃描線O(N)。

二元樹刪除N個點、加入N個點、尋找5N個點,O(NlogN)。

時間複雜度O(NlogN),可以找出所有最近點。

原理是平面切割成左右兩側,答案分為「最近點左側」、「最近點右側」、「最近點橫跨兩側」三種情形。

先將左側右側遞迴求解後,利用左側右側答案,算出橫跨兩側答案。

點分為左側右側,點數盡量多。

於兩個點同一個集合點,我們可以遞歸求解。

最近点 Play

延伸閱讀…

平面最近點對

最近點對:詳解4種不同複雜度之算法

左右兩側是。

左側、右側遞迴求解,後求得這兩種情況下最近點。

最近點對距離d。

左側每一個點,半徑d以內範圍(包含邊界),會有其他左側點存在,可能出現另一側點。

右側亦然。

要找出橫跨兩側點,可以左側右邊線,左距離d以內範圍裡(包含邊界)這些點,有可能右側點形成最近點。

左側右邊線,往右距離d以內範圍裡這些點,是可能另一頭端點範圍。

要找出橫跨兩側最近點,只要依序舉左側右邊界距離d以內、位於左側點,作為左端點;找左側右邊界距離d以內、位於右側點,作為右端點。

一、理解方式,是下往上掃描左端點;每個左端點,找到右端點。

右側之中,點點之間距離是d。

運用先前分析手法,我們只需檢查掃描線中下兩點、上兩點,作為右端點,能囊括所有橫跨兩側最近點。

此處省略分析過程,讀者可以自行畫圖觀察。

實作時,運用右側掃描線作為輔助,跟隨左側掃描線步趨,能找到中下兩點、上兩點,而使用二元搜尋法。

二、另一個理解、但是實作方式,是範圍內這些點全部混一起,分左右,然後下往上掃描。

舉下端點,尋找上方鄰近點作為上端點,檢查是否形成最近點。

最多隻需要檢查下端點後四點,囊括所有橫跨兩側最近點。

此處省略分析過程,讀者可以自行畫圖觀察。

複雜喔!可以先將左側、右側分佈情形分開畫,左右兩側拼在一起。

進一步。

第四點同側另外一點形成最近點,後是能檢查到,所以檢查第四點、只需要檢查後三點。

以上圖例是掃描線掃中左側點,讀者可以自行分析掃描線掃中右側點所有情況。
最近点

大功告成。

教科書談到後五點;此處深入分析,後五點逼近後三點。

儘管推理過程讓人感覺有學問,但是執行時間於舉法。

這種鑽牛角尖實際學問,不如不學。

一羣點中,距離兩個點叫作「點對」。

舉法,時間複雜度O(N²),可以找出所有點。

旋轉卡尺,時間複雜度O(NlogN),可以找出所有點。

離變,擴張。

無論是擴張兩點連線,或者是擴張邊界,距離是變。

要找點,可以使用擴張邊界概念。

擴張邊界直到,碰觸到點,後形成凸包。

因此,點對是凸包對角線。

日常生活中,邊邊角角寬度是、卡到。

凸多邊形範圍內,距離是角線距離。

凸多邊形範圍內任一線段,於、等長於某一條角線:接下來n行:每行兩個實數:x y (0≤x,y≤10^9),表示一個點行座標和列座標,中間一個空格隔開。

僅一行,一個實數,表示短距離,精確到數點後面4位。

眾所周知,這道題可以分治解決。

我們可以這n個點,x座標第一關鍵字,y座標第二關鍵字排序。

排序後,我們可以這個點集分成左右兩部分。

這樣分割後,所有有可能成為答案分為了三部分——1.兩個點左側集合,2.兩個點右側集合,3.一個點左側集合、另一個點右側集合。

於兩個點同一個集合點,我們可以遞歸求解。

延伸閱讀…

詳解平面點集最近點對問題

Point

但於兩個點同一個集合最近點對求解,是棘手。

如果暴力兩側枚舉點然後求距離,時間複雜度會退化為

O

(

n

2

)

O(n^2)

O(n2),分治沒有意義了。

兩側暴力枚舉點,會得到很多多餘信息。

如果分治處理左右兩邊時候,求出的最近點距離d(d左半集合答案 與 右半集合答案 小值),那麼如果我們能確定左側一個點到右側任何一個點距離於d話,那麼這個點枚舉點過程中是可以直接。

這樣,我們可以左側集合靠右點橫座標 記為

m

i

d

x

midx

midx,如果一個點橫座標x滿足

x

m

i

d

x

d

|x-midx| \geq d

∣x−midx∣≥d,需要考慮這個點了,需要考慮點只有中間一個“豎條”中點。

上圖中綠色點是可能對答案造成貢獻點,橫座標於於midx點集左側集合,剩餘的點是右側集合。

(黑點是輔助作圖用,請忽視這些點)如果中間這個豎條中暴力枚舉點對話時間複雜度是(總時間複雜度是

O

(

n

2

)

O(n^2)

O(n2)),我們可以進行進一步優化——如果兩個點縱座標於d,那麼它們間距離於d。

這樣我們可以所有綠點取出來,y座標第一關鍵字,x座標第二關鍵字排序(其實有沒有第二關鍵字無所謂),y座標到考慮每個點i它後面(即縱座標於於它點)所有點j之間距離,如果ij縱座標差於於d退出循環,繼續枚舉j了,這樣可以得到結果。

這個算法時間複雜度是

O

(

n

log

2

n

)

O(n\log^2n)

O(nlog2n)了,這是什麼呢?因為我們枚舉j時,會及時break,所以於某一個點,有機會它求距離點一個面積

2

d

2

2d^2

2d2“日”字形區域中。

上圖”日“字形兩個邊長d正方形組成,是有機會黃點求距離點座標可能範圍。

這個範圍,以至於這個範圍內點會超過6個!這個結論,我們現在來證明這一點。

因為這個日字形隸屬於右半部分,右半部分任意兩個點間距離是於於d(前文d定義可知),所以“日”字形中包含所有點對距離應該是於於d。

我們可以這個“日”字進行一個劃分。

於2d邊,我們取其三等分點;於d邊,我們取其中點。

這樣,我們這個日字形劃分六個面積相等小矩形。

矩形

2

3

d

\frac{2}{3}d

32​d,

1

2

d

\frac{1}{2}d

21​d,勾股定理可知,改矩形對角線

5

6

d

\frac{5}{6}d

65​d。

矩形對角線長度是一個矩形中所有點之間距離,而

5

6

d

< d \frac{5}{6}d

這樣證明,每個點時間貢獻常數級別,中間“豎條”(“豎條”定義見上文)間點最近距離求解時間複雜度

O

(

n

)

O(n)

O(n),排序需要

O

(

n

log

n

)

O(n\log n)

O(nlogn)時間,所以這個部分總時間複雜度

O

(

n

log

n

)

O(n\log n)

O(nlogn)。

其實“豎條”中midx左半部分點右半部分分開考慮,時間複雜度變,因為每個點答案貢獻常數(i出於midx同側點中畫一個“日”字形,裏面點數多只有6個,

2

×

6

=

12

2 \times 6 = 12

2×6=12)。

因為這樣寫起來代碼,所以我代碼中採用了這種寫法。

注:這個代碼是 2018 年時候寫,當時懶,合併時候直接使用了

s

o

r

t

sort

sort 排序,這種做法時間複雜度

O

(

n

lg

2

n

)

O(n\lg^2 n)

O(nlg2n),當時沒指出來,算是給自己留了個大坑吧。

直到 2022 年我這個坑填了。

和曦神鵬犇打完

47

47

47 屆 ICPC 瀋陽站,

37

37

37 名,銀牌第二,一起去日新樓吃飯,鵬犇請客。

我們突然討論到這道題,然後我時間複雜度證説錯了。

我後來仔細地想了一下,於我現行做法,應該上面時間複雜度證中“日字形”橫着畫,才能説明時間複雜度是

O

(

6

n

)

O(6n)

O(6n)。

這個問題各種面試當中出現,,很少有人能答上來。

説話,我問過,因為毫無準備,所以沒有答上來。

是,這道題有點,沒有準備人往往答上來。

我們看下題意吧,題意,一個平面中分佈着n個點。

現在我們知道這n個點座標,要求找出這n個點當中距離最近兩個點間距。

我確定這個問題是否出自於天文學,但是它放到天文背景當中。

想象一下宇宙當中,存在着無數星辰,我們想要找到其中距離最近兩顆天體。

它們有可能是雙子星,有可能是伴星系……這麼想想,有沒有覺得?我們來分析一下問題,會發現一個矛盾之處。

矛盾地方於如果我們要求出每兩個點之間距離,那麼複雜度是,因為n個點取兩個點一個有種可能。

如果存在算法,那麼我們不能求出所有點之間距離,但如果我們所有距離沒有枚舉過,如何可以判斷我們找到是呢?我當時面試時候這麼回答,雖然我們現在知道這個説法是錯,但是如果沒有這一層信息,你能判斷嗎?如果我們仔細思考一下,會發現這個問題和排序其實類似。

因為我們排序時候,表面上每兩個點之間存在大小關係,我們要排序要獲得這些關係。

但實際上,我們知道,無論是排序還是歸併排序可以做到時間內完成排序。

無論是排序還是歸併排序,本質上是利用分治法。

那麼這道題是否可以使用分治法求解呢?答案是可以,既然是使用分治法,那麼我們要做拆分,整個數據拆成兩個部分,使用遞歸完成兩個部分,然後合併得到結果。

這個問題當中,我們要拆分數據,只需要x軸座標所有點進行排序,然後選擇中點進行分割,分割後我們得到結果如下:拆分結束後,我們只需要統計左邊部分最近點、右邊部分最近點,以及一個點左邊一個點右邊最近點。

於前面兩種情況解決,我們只需要遞歸可以搞定了,但於第三種情況應該怎麼辦?這是本題難點所在。

要分析這個問題不是非常容易,需要深入思考,我們通過遞歸調用可以獲得左邊部分SL短距離D1以及右邊部分SR短距離D2。

我們取,左右兩邊短距離最小值,這個應該理解。

求出了D後,我們可以它來限定一個點SL一個點SR這種情況點對範圍了,不然我們要兩邊各有n/2個點情況,計算複雜度。

我們來分析一下問題,我們左側選擇一個點p,我們來想一個問題,於點p而言,SR一側所有有可能它構成最近點嗎?不是,有一些離得是可能,於這些點我們沒有一一遍歷,直接可以批量。

要想和p點構成最近點,下圖這個虛線框起來範圍內。

這個虛線構成框是一個方形,它是D,是2D。

這是怎麼呢?,於p點來説,要想和他構成全局最近點,那麼距離它距離要於目前優解D。

既然距離要於D,那麼意味着它們橫縱座標絕對值要於D。

這個框只是我們直觀看到,實際算法運行時候是沒有這個框,我們只能座標軸自己去進行判斷某個點在框裏。

有了這個框後,我們產生了另外一個問題,那這個框可以起到多作用呢?有了這個框可以降低算法複雜度了嗎?會會出現右側所有點框裏端情況呢?我們只需要分析一下會發現這是可能,不僅可以判斷出這是可能,而且我們可以得出另外一個結論。

,我們證一下什麼右側所有落這個虛線框裏是可能。

我們端的情況,端的情況我們選中p點分割線上。

那麼它畫出來框應該全部落SR區域,畫成圖是這樣:但是我們想一下會發現一個問題,這個虛線框裏點數量可能是。

因為於框裏點我們有一個基本要求,這個框裏並且SR區域內點,兩兩之間距離不得於D。

如果於D了和我們才得到D是左右兩側距離結論矛盾了。

那麼上面圖中情況是可能,因為這麼多點聚集一起顯存兩個點距離於D了,這矛盾了。

説於存在這個距離限制,能夠落這個虛線框裏點數量是,而且這個數量大家想要,有多呢?到多只有6個,下面這種情況:上圖當中,一共有6個點,這6個點兩兩之間短距離是D,這是端的情況。

無論我們如何往其中加入點,會產生兩個點之間距離於D。

這是我們感受,有沒有辦法證明呢?是有,我們可以這個矩形進行六等分變成下圖這樣:我們來分析一下,上圖每一個小矩形是,是,它對角線是。

那麼鴿籠原理,如果我們放入超過6個點,會存在一個小矩形內存兩個點。

而小矩形內距離於D,説這兩個點離於D,這和我們之前假設矛盾了,所以可以得出超過7個點情況是存在。