上篇文章介紹了分治法的概念和基本解題步驟,並附加了一個例題幫助大家了解分治法的基本思想,在這篇文章中,我將對分治法的另一個經典問題進行分析,希望我的文章能夠將今天的主題解釋清楚。接下來我將用三種不同的方法求解“平面最近點對”問題。
問題描述:在一個平面上隨機分布着 n 個點,現給定 n 個點的坐標,要求給出最近的兩個點之間的距離。
方法一:原始方法
題目要求求出最近的兩點之間的距離,先整理一下已知的線索:首先點的總個數為 n ;其次已知 n 個點的坐標。掌握了每個點的坐標,就相當於間接地掌握了任意兩點之間的距離。假設兩個點為 A : ( x1 , y1 ) , B : ( x2 , y2 ) ,兩點間的距離為 distance ,根據平面坐標系中的兩點間距離公式可得:
distance ^ 2 = ( x1 - x2 ) ^ 2 + ( y1 - y2 ) ^ 2,
運用該公式對每兩個點的距離進行計算,並不斷更新最小值 min_distance 。
核心代碼為:
這個方法很直觀也最容易想到,不過,由以上的兩層循環可知,該方法的時間復雜度為 o ( n ^ 2 ) ,當 n 的值不斷增大時,這個方法處理起來就顯得力不從心了。因此我們必須尋找另一種更有效的方法,或者在此方法上進行適當的改進。
接下來一起了解一下第二種方法--分治法
方法二:分治法
為了做到有理有據,正式敘述解法之前,我得再啰嗦幾句選擇分治法的原因,希望不會引起大家的反感。在本問題中,需要求得最近兩點之間的距離,整個問題的規模為 n 個點,不妨將這 n 個點一分為二,就變成兩個求解 n /2 個點規模下最近點對的距離問題, 如此不斷縮小規模,當變成兩個點的規模下求解最近點對問題時,顯而易見,即為這兩個點的距離,這樣隨着問題規模的縮小解決的難易程度逐漸降低的特征正是可以用分治法解答的問題所具備的特征。
接下來,我們按照分治法解題步驟 分割--求解--合並 分析這個問題。將n 個點分為兩部分后,最近的兩個點有可能出現在第一部分中,也有可能出現在第二部分中,當然還有可能一個在第一部分中,另一個在第二部分中,於是我們可以分別求出三種情況下最近點對的距離 dis1 dis2 dis3 ,從這三者中選擇出最小的一個作為本規模下的解。
分割:將 n 個點分為兩部分,每部分包含 n /2 個點,
求解:分別對兩部分按照同樣的方法求得最近距離 dis1 和dis2 ,
合並:求解兩點分別分布在兩個部分的情況下的最近距離 dis3 ,取 dis1 dis2
dis3 三者中的最小者。
思路如上,我們再對具體的細節進行完善
如何求子規模下的最近距離?
我們可以使用遞歸調用的方法來實現子規模的求解,遞歸有終止條件,這個問題的遞歸終止條件應該是當規模為 2 時,最近點對的距離即為這兩點的距離。
如何求合並后兩點分屬兩個部分時的最近距離?
由於兩點分屬兩個部分這種情況下,找尋任意一個點的范圍都太過寬泛,因此我們需要使用技巧來縮小尋找范圍。
這時候我們假設從兩部分分別求出的最近距離為 dis1 和 dis2 ,
dis = min( dis1, dis2 ),再假設分屬兩部分的情況下,A ( x1 , y1 ) 屬於第一部分 ,B ( x2 , y2 )屬於第二部分,且以 x 軸作為分割的量,我們可以斷定 如果存在解 ,A 、B 兩點一定處在中心分割線兩側 2*dis 長度之內的區間內,(考慮到極端情況下有一點處在分割線上的情況,因此不能選擇 dis 長度區間),示意如下圖:
中心分割線為 x = mid ,ans = dis ,那么,A 點一定存在於 x = mid - ans 和 x = mid 這兩條直線之間的區域,同時, B 點一定存在於 x = mid 和 x = mid + ans 這兩條直線之間的區域。
這時候,只要在中軸線左右兩側分別不超過 dis 的距離范圍內進行求解即可。
我們可以逐個枚舉x = mid-dis 與 x=mid+dis 兩條線之間所有點的兩兩距離。在處理之前我們可以做一些准備工作進一步優化求解過程。首先將所有點按照 y 坐標值升序進行排序,這樣做有一個好處:當某一點 A的縱坐標 y1 與 某一點 B 的縱坐標 y2 滿足 y2 - y1 > dis 時,可以直接斷定當前的 A點與 縱坐標大於 y2 的任意一個點的距離都會超過 dis ,也就不用判斷其余的點,省去了不少時間。
思路已經描述完畢,接下來為大家奉上本方法的核心代碼。
下面給大家分享第三種方法。
方法三:分治法改進版
在完整介紹這種方法之前,我們先來了解一個知識
如上圖所示,上圖中有一個邊長為 L *2L 的矩形,將該矩形等分成六個 1/2 L *2/3 L 的小矩形。假設在該區域內散布着若干個點,並且任意兩點之間的距離不小於 L ,請試着確定一下最多可以分布幾個點?
答案是 最多可以分布六個點,每個小矩形內最多可以分布一個點。
我們用反證法來證明一下這個結論:
假設 分布的點數多於六個
由假設可知,
必然存在一個甚至多個矩形內有多於一個點;
同時,處在同一塊矩形區域內的兩個點的最大距離 ma x_dis 不會超過對角線的長度。在 1/2 L * 2/3L 的矩形中,對角線長度為
sqrt( ( 1/2 L)^2 + ( 2/3 L )^2 )= 5/6 L , max_dis <= 5/6 L < L ,即最大距離不超過 L ,這與題目的要求相違背,因此,點數不會超過 6 。
再看下面這張圖片:
已知:
中心軸線將一塊區域分成 1 區 和 2 區 兩個區間長度均為 L 的區域;
1 區 和 2 區中散布着若干個點,並且在 1 區內部任意兩點之間的距離大於 L ,2 區也是如此;
我們稱 1 區內的點為 A , 2 區中的點為 B ,A 、B 兩點的極限位置可以在中心軸線 x = mid 上,如上圖;
我們需要求滿足以上條件的 A、B 兩點的最近距離是否存在小於 L 的情況。
由以上條件,我們可以推測 當 A ( x1 , y1 ) 點的位置確定的時候,B ( x2 , y2 ) 點可能位置也必然確定,
即 y2 >= y1 - L 且 y2 <= y1 + L , x2 >= mid 且 x2 <= mid + L ,
換言之,在上圖中,只要 1 區 的點處在 A 點所在的水平線上,那么 2 區 中點的可能位置一定在圖中的矩形區域中,因此,對於 1 區中的每個點,在 2 區中最多需要判斷 6 個點就可以下結論。
有了上面的知識鋪墊,那我們可以利用以上知識進一步優化分治法求最近點對距離的方法,可以預見對第二種方法改進的地方發生在合並兩個子集合時對兩點不屬於同一個子集合的處理上,現在的任務就是對於 1 區的任意一個點,求出對應 2 區范圍內需要判斷的數目不超過六個的點都有哪些。我們可以在找這六個點的時候先用橫坐標限制范圍,再用縱坐標限制范圍。每求一個點,都確定一次 2 區亂序的點哪些在范圍內和遍歷所有點實現上是一樣麻煩的,我們需要有突破。這時候如果能將 2 區所有的點按照縱坐標進行排序,那么我們只要求得上、下界就可以了。為了省事,一般只求得上、下界之一,並連續的判斷6個點即可。
具體的實現思路是,建立兩個點集,集合一按照橫坐標 x 的升序進行排列,集合二按照 y 的升序進行排列,同一規模下利用集合一求出 1 區 和 2 區的區間長度 L ,再利用 L 將集合二中處在 1 區 和 2 區范圍內的點按照之前在集合二中的順序挑選出來。接着逐個枚舉 1 區中的點,確定該點對應在 2 區中需要判斷的六個點並給予精准的判斷。
下面我將此方法的核心代碼向大家展示,文字沒有解釋清楚的地方,希望代碼部分能夠補足。
打開網頁http://paste.ubuntu.com/25154215/查看第三種代碼。
留言功能已經開啟,望各位發現文章中解釋的不夠清楚的或者存在錯誤的地方,在下方留言區進行補充和指正,不勝感激。