Divide and Conquer

Divide and conquer 即系將一個大問題切成一D小問題(小問題可能系大問題既 General case 或者 Special case,亦可能同大問題完全無關),通過解決小問題之後組合解決大問題。

由於可以將問題抽象化,變為 General case 同 Special case,一般 Divide and conquer 都比較容易思考同 Prove。至於 Implementation 則可能用 Recursion 或 Loop(一般其實系將 Recursion 變為 Loop)。

1. Finding maximum in an array

可能你一眼望埋個問題已經有答案:用 for loop through the array, compare the current maximum and the array item, and store the maximum value.

問題系,你點知呢個做法是否正確呢?換句話來講,即係你點 Prove 呢? 其實用 For loop 已經唔系最直接既做法,最直接既做法其實系 Recursion,但你可能由頭到尾都冇諗過。以下系唔同既 Case。

  1. Special: 如果個 Array 得一個 Item,maximum 就肯定系果個 Item。(好似好廢,但其實系重點)
  2. General Case: Given 2 個 Array 同佢地最大既 Item,佢哋 Combine 之後既 Array 既 Maximum 就係兩個最大 Item 裡面最大果個。
  3. 所以如果有一個 Array,只需要檢查是否 Special case,如果系就直接 return 裡面個 item;唔系既話就切成兩個較小既 Array,搵出佢哋既 Maximum,最後 return 佢哋之間最大既。

所以其實系點做 Recursion 既呢?以下系條pseudo-code

 

至於點變為 Loop 呢?其實好簡單,array_max(array, start, start) 其實同 array[start]冇分別,而之後 Recursion 到 array_max(array, start+1, end) 就可以視作 counter + 1 處理。

 

2. Binary search

Binary search 系針對 sorted list 既 search,原理其實都好簡單,都係 recursion。

  1. 假設個 list 系 ascending 既。(如果系 descending 就講前後調轉)

  2. 如果個 list 根本冇野,咁就肯定唔會搵到啦,收工。

  3. 如果個數 = 個 list 中間個 element => 搵到收工。

    1. 如果要搵既野大於中間個 element,就代表要搵既 element 只可能系個 list 既後半部分(因為ascending)或者根本唔存在。就對個 list 既后半部分做 Binary search (recursion)
    2. 如果小於,就代表要搵既野只可能系個 list 既前半部分或者根本唔存在。就對個 list 既前半部分做 Binary search (recursion)
 

要改為 Loop 甚至更簡單,因為呢個其實系 Tail Recursive,簡單來講即係 return value = return value of the next recursive call.

 

注意,一切二分(Bisection)既野(只論二分既部分),如 Binary search,既 Time complexity 系

3. Merge sort

  1. Special case: 如果個 List 得一個 Item,佢一定系 Sort 好既。

  2. General case: Given 2 個 sorted list,你可以好快將佢哋 combine 做一個 sorted list

    1. 每次將兩個 list 中最大/最小既 item 放入目標 list裡面。方法系比較兩個 list 既第一個 item,要最小/大既。(兩個list 裡面最小/大既 item 必定系第一個,否則就唔系 sorted list)

      呢個方法既 Time complexity 系 ,因為一共要放 n 次(假設兩個 list 既 item 總數為 n)

為左夠快,merge sort 系分落去既步驟用左二分既方法:即把個 list 分為前後兩半,Recursion 直至得 1 個 element。所以佢既 time complexity 系 (層數 = ,每層需要做既 merge 既 item 總數必定為 n)

 

4. Quicksort

其實已經 Out syl,但因為個 Algorithm 好正所以順便講埋。

Quicksort 理論上速度比 merge sort 慢,但因為佢可以做到 in-place sorting (merge sort merge 既時候一定要個 temporary array,所以唔可以 in-place),所以實際效率比 merge sort 好。

Quicksort 重點系搵 Pivot element。Pivot element 決定 Quicksort 會點將個 Array 劃分為兩部分:一部分比 Pivot element 小,一部分比 Pivot element 大。選擇得好既話(median),個 Array 就會被分為兩部分,即二分,效率最高,但我地系 sort 之前其實系唔會知道個 Array 既 median 系幾多,所以要睇運氣。

Quicksort Pivot 位置選擇可以造成 Worse case,為左避免 sorted array可能造成 worse case 既情況,所以實際使用時一般會用 random pivot,但為左方便起見,例子唔會用 random pivot,而系用 largest index。

下方解釋以 ascending 為例。

  1. 我地將 Array 分為兩部分:前半部分比 Pivot element 小,後半部分比 Pivot element 大或一樣。(注意,一般情況下這並非 sorting,而呢個部分就係精粹,建議唔理解既話 Dry run 一下)

    1. 想象個 Array 分為三部分:< Pivot, >= Pivot, unknown。(1、2部分一開始長度為 0,unknown部分為全部)你需要 manage 幾個 variable:

      1. lower: Array 第一個 element 既 index
      2. higher: Array最後一個 element 既 index,亦系 pivot index
      3. less = lower - 1,即 < Pivot 部分最後一個 element 既 index
      4. i = lower,即 unknown 部分第一個 element 既 index

      然後直接上 code 算

       
  2. 之後只需要對兩部分做 Recursion,總會做到得返兩個 element,而兩個 element 做完 partition 就係 sorted。