【演算法】分治法(Divide and conquer)

朱痕染跡璧有瑕
8 min readNov 4, 2020

--

https://pixabay.com/photos/fern-plant-green-wild-weeds-3016113/

本文為交通大學開放式課程上課心得整理。

分治法與數學歸納法之間的關聯非常強,因此在開始介紹演算法之前,要先整理一點觀念。

數學歸納法

所有的數學歸納法證明都包含兩個步驟:basis step 以及 inductive step,basis step 是比較簡單的例子,在極小的問題維度之下提出圓滿的解決方法,inductive step 則根據 basis step 展開,讓問題放大之後套用前述邏輯也能正確處理問題。inductive step 的證明方式有兩種:

・weak induction:先假設演算法在步驟 k 的時候是正確的,再往前推進一步證明 k + 1 也是正確的,以此證明所有的 n 都會是正確的。

・strong induction:與 weak induction 相反,strong induction 每次前進的步驟不見得都是一,也可能一次橫跨許多步驟,直接假設演算法在 ≤ k 的時候都是正確的,並證明 k + 1 時也是正確的。

Divide and conquer

Divide and conquer 基本上分成三個部分,divide、conquer 以及 combine,divide 指的是將整個問題切小,但必須確保問題的題型是一樣的;完成之後再將每個小部分都以遞迴(recursive)的方式處理,這就是 conquer,最後再把每個小部分都結合起來(combine),即是最終答案。

以下介紹幾個使用 Divide and conquer 操作的問題(在資料結構有敘述的部分會略過不再重複紀錄):

Merge Sort

給定一串數字,將數字按照大小排序好之後回傳。

https://ocw.nctu.edu.tw/course_detail-v.php?bgid=8&gid=0&nid=493&v5=62uS-MOv0bY,53 分 56 秒截圖
同上,57 分 04 秒截圖

divide 與 conquer 的時間是固定可以算出的,因此演算法的最終目標是要在 O(n) 的時間內完成 combine,讓時間可以 bound 在 O(n log n) 上。

https://ocw.nctu.edu.tw/course_detail-v.php?bgid=8&gid=0&nid=493&v5=sweW4TVFpbQ,6 分 14 秒截圖

在 merge 時,我們會先開一個額外的空間,將已經排序好的兩個部分抄下來,最後再寫回去。由於切開的兩個部分各自已經排序好,因此可以假設最小/大的元素會在第一個,此時只要比較第一個元素,將比較小/大的放入暫存空間中,再依序往下比較,即可得到完整排序好的陣列,時間複雜度為 O(n)。

在時間複雜度的表現上, merge sort 與 heap sort 是一樣好的,設計者可以自行判斷使用時機。當資料形式很單純(如陣列),可以用 index 抓到資料時,便可以使用 heap sort;反之如果輸入的資料比較複雜(如 linked list),那便可以考慮使用 merge sort。

Recurrence Relation

演算法很重要的一個環節就是執行時間,接下來介紹如何分析執行時間。

同上,15 分 03 秒截圖

・Unrolling the recurrence(recursion tree):先展開問題找出規律,證明完成之後再將結果加起來即可。

同上,29 分 10 秒截圖

・Substituting a guess:先預想一個答案,再用數學歸納法證明猜想正確。

同上,33 分 27 秒截圖

Counting Inversions

Counting Inversions 是 merge sort 題型的延伸,可以用來計算兩個序列的相似度,用來定義兩者之間程度不同的情況便稱為 inversion。

同上,39 分 54 秒截圖

以上圖為例,可以先固定其中一個序列,再與另外一個序列配對,看看有多少個交錯(crossing),就是 inversion 的程度。由於 base rank 已經排序好了,所以我們只要計算要比較的 rank 從該 element 往後算有多少個 inversion,就可以知道交錯的程度,這種計算方法的時間複雜度為 Θ(n2),但如果應用前面學到的 merge sort,就可以將時間降到 O(n log n)。

同上,43 分 02 秒截圖

如前所述,分治法最困難的地方就是如何有效率的 combine,只要能在線性時間之內完成 combine,就可以保證演算法的時間複雜度可以控制在 O(n log n) 中。實現的概念為當每個小部分在處理 inversion 時順便排序,combine 執行兩部分 inversion 比較時,由於已經排序,就只要比較陣列中的部分元素即可。

同上,56 分 59 秒截圖
同上,60 分 11 秒截圖

Closest Pair of Points

這也是很常遇到的問題,主要是探討如何在許多平面的點中,找到直線距離(Euclidean distance)最近的兩個點。

這個問題如果直接計算平面上每兩個點之間的距離找出最小值,所需的時間複雜度為 Θ(n2),因此在此我們一樣要試著使用分治法,讓時間複雜度降到 O(n log n)。

這個問題可以先簡化成一維空間來思考。如果今天所有的點都在同一條軸上,想知道兩點之間最近的距離,可以先將所有的點由小排到大,此時距離每個點最近的點一定會在其右側或左側,其他的點就不用考慮。在這個基礎上,再將問題延伸到二維空間,我們先假設所有點的 X 座標都不同,將平面上的點分別切成左右兩邊(數量皆為 n/2),此時有可能產生最近距離兩點的地方為:

・左右兩邊的兩個點(兩個點都在同一區)
・橫跨左右兩邊的兩個點(兩個點在不同區)

只要分別計算出這三個條件兩點之間的長度,找出最佳解即可。

同上,72 分 19 秒截圖

這個問題的困難點一樣是 combine 時的處理方式,由於我們先將平面分成左右兩邊,假設左側最右邊的點的 X 座標為 X*,切分線 L 的方程式則為 X = X*,接著先找出左右兩側兩點間最短的距離,只要在離切分線左右兩側最短距離內,找出是否有比同區內兩點距離還小的兩個點即可。

同上,75 分 01 秒截圖
同上,80 分 05 秒截圖

為了限縮比較時間,我們可以先將落在比較區塊內的點以 Y 軸為基礎做排序,假設以高排到低,此時只要往下比較 15 個點,就可以完成最兩個區塊最短距離的計算。相關推論如下:

・假設比較區塊的距離為 δ,先將左右兩側的區塊再分割成 δ/2,因此每個方塊都面積都是 δ/2 * δ/2,我們要證明兩件事:

・每個方塊裡面至多只會有一個點存在
・兩個落在區塊內的點 S 與 S’,其距離不會超過十五個方塊

第一個推論可使用反證法:

・假設 S 與 S’ 在同一方塊內,則兩者之間的距離必定小於 L 到兩點的距離,也就是兩個點會在同一側,如此便和上面找到的最短距離相悖,因此兩點必定在不同的方塊內,故得知每個方塊至多只會有一個點存在。

第二個推論一樣使用反證法:

・假設兩點之間的最短距離超過十六個方塊,則距離會超過 3/2δ,已經大於 δ,不會是最小距離,故不需計算。
同上,88 分 33 秒截圖

完成的 pseudocode 如下:

同上,89 分截圖

相關連結:

--

--