1. 程式人生 > >【演算法導論】【筆記】【分治法】最近點對問題

【演算法導論】【筆記】【分治法】最近點對問題

尋找最近點對

目錄

題目:在一個 n2 個點的集合 Q 中尋找距離最近的點對

問題分析

首先很容易想到暴力破解的方法,但是需要 O(n2) 的時間複雜度,所以考慮使用分治法,但將複雜度降為 O(nlogn) 還需要一些優化。 (關於執行時間的遞迴式為 T(n) = 2T(n2) + O(n))。

分治法

思路:把點集分為兩半,最近點對可能全部屬於左半點集,或者右半點集,或者一個屬於左半點集,一個屬於右半點集。所以最終結果為這三種情況的最小值。

分治法一般分為三個步驟:

  • 分解:將 n 個點按照橫座標排序,並按照 1~n 編號,分成兩部分:1~
    n2
    n2~n。
  • 解決:分別求出左右兩部分中的最近點對距離,記為 d1d2,取 d=min(d1,d2)。時間複雜度為 2T(n2) (這裡通常採用遞迴的辦法)
  • 合併:這一步考慮的問題是,最近點對可能分別來自左半部分點集和右半部分點集,所以如果存在這樣一個最近點對距離比 d 還小,那麼返回該值,否則返回 d。

“分解” 和 “解決” 部分很好實現,重點在於如何實現合併

首先我們不能把左半部分點集中的點挨個與右半部分的所有點算距離,這樣總體來看跟暴力破解就沒什麼區別了,所以需要優化,減少需要計算距離的點對個數。優化的辦法是:

  • 選出與中間結點橫座標距離內為 d 的點:遍歷整個點集,篩選出一些點,這些點滿足與中間結點的橫座標距離小於 d(橫座標距離大於 d,那該點到另外一個點集中任意一個點的距離肯定都大於 d)。這一步時間複雜度是 O(n)。
  • 只需計算緊隨其後的 6 個點:接下來直接對篩選出的點進行暴力破解就可以了。但是篩選出來的點可能很多,所以需要繼續優化:將篩選出來的點按縱座標排序,縱座標距離大於 d 時,直接 break。而且對於每個點來說,最多隻需要考慮緊隨其後的 6 個點(證明在《演算法導論》P612)。這裡需要的時間複雜度為 O(n)。

時間複雜度

到目前為止,演算法的時間複雜度的遞迴式是 T(n) = 2T(n2) + O(n) + O(nlogn), 要達到目標是 T(n) = 2T(n2) + O(n),還需優化排序帶來的 O(nlogn)。

《演算法導論》中給出的是方法是 “預排序”:在第一次遞迴呼叫前,就把點集 P 分別按照橫座標和縱座標排好序得到陣列 X 和陣列 Y,然後在傳進函式。不用再遞迴函式內部進行排序操作。所以總演算法複雜度為 O(nlogn)。

但這樣就會有一個問題,每次遞迴前,會將陣列 X 和陣列 Y 折半劃分為 XLXRYLYR 傳入函式,如何保證 XLYL(或者 XRYR)元素來自同一個集合。根據《演算法導論》提到的思路,利用 “陣列 X 按照橫座標升序的特點” 可以線上性時間內解決這個問題的辦法:

  • 陣列 X 直接對半分得到 XLXR
  • 遍歷陣列 Y,小於陣列 X 中間點的橫座標的元素放入 YL,否則放入 YR

因為 YLXL 中所有點的橫座標都小於 X 中間點的橫座標,必然屬於同一點集,且 YL 按照 Y 排序的。