演算法基礎--遞迴入門の歸併排序

本文只是自己的筆記,並不具備過多的指導意義。
程式碼的初衷是便於理解,網上大神優化過的程式碼很多,也不建議在專案中copy本文程式碼。
目錄
- 歸併排序
- 如何合併兩個有序陣列
- 使兩端分別有序
- 最外側還有一個入口方法
- 圖解排序過程
- 遞迴時間複雜度的估算
- master公式
歸併排序
眾所周知,分治策略中使用遞迴來求解問題分為三步走,分別為分解、解決和合並。
所以歸併排序中心思想是通過二分的方式,將一個段陣列拆分成左右兩端,然後進行合併
-
如何合併兩個有序陣列
通過將最小值依次外排的方式,進行合併
- 兩個指標
p1
,p2
指向兩個陣列的首位置,每次將較小的值外排進輔助陣列
並且右移指標
。 - 當一個
指標越界
後,將另一個剩餘的元素放入輔助陣列
。 - 最後的
輔助陣列
將是一個有序
陣列。
/// 對一個數組的兩個有序分段進行整合排序 /// /// - Parameters: ///- arr: 陣列 ///- left: 左側起點 ///- mid: 中心點,左側末尾 ///- right: 右側末尾 func merge(arr: inout [Int] ,left: Int ,mid: Int ,right:Int) { var help : [Int] = [Int](repeating: 0, count: right-left+1) //輔助陣列 var p1 = left//左側指標 var p2 = mid+1 //右側指標 var i = 0 //容器指標位置 while p1<=mid && p2<=right { if arr[p1] <= arr[p2] {//左側指標位置小於等於右側指標位置 help[i] = arr[p1] //將左側指標位置放入輔助陣列 p1 = p1+1 //左側指標右移 }else {//右側指標大於左側指標位置 help[i] = arr[p2] //將右側指標位置放入輔助陣列 p2 = p2+1 //左側指標右移 } i = i+1//容器指標右移 } //到這裡,p1,p2之中已經有一個指標越界了 //沒有越界的那個,重複上面的操作寫入輔助陣列 while p1<=mid { help[i] = arr[p1] p1 = p1+1 i = i+1 } while p2<=right { help[i] = arr[p2] p2 = p2+1 i = i+1 } //寫入完畢,用輔助陣列替換進原陣列 i = 0 for index in left...right { //left,left+1...right-1,right arr[index] = help[i] i = i+1 } }
之所以先說這個,是因為這個方法是核心方法,並且寫完滯後直接將可以測試。
測試通過之後,再去測試下面的遞迴方法可以更加準確的確定問題所在。
-
使兩端分別有序
通過遞迴的方式,將長陣列最終分解成有序的陣列進行排序處理。
比如 [4,2,6,1,9,7]
/// 在一個數組的某一段上進行排序 /// /// - Parameters: ///- arr: 陣列 ///- left: 左側 ///- right: 右側 func mergeProcess(arr: inout [Int] ,left: Int ,right:Int) { if left==right { //左右相等,說明這次要調整的只有一個數 return } let mid = (left+right)/2 //取得中心點位置 mergeProcess(arr: &arr, left: left, right: mid)//對左半邊進行排序 mergeProcess(arr: &arr, left: mid+1, right: right)//對右半邊進行排序 //此時左右兩側都已經排序完成 merge(arr: &arr, left: left, mid: mid, right: right)//對左右兩側進行合併排序 }
-
最外側還有一個入口方法
/// 歸併排序 /// /// - Parameter arr: 陣列 func mergeSort(arr: inout [Int]) { if arr.count<2 { return } mergeProcess(arr: &arr, left: 0, right: arr.count-1) }
時間複雜度 O(N * logN)
,額外空間複雜度 O(N)
。
-
圖解排序過程

網上還看到一個動圖,結合上面的應該更容易理解了。

這個過程中,不管陣列多長,最後都將被劃分成2個單位長度的陣列進行排序。
然後向上依次合併並且排序。
這也很好的體現出了分治或者遞迴的思想所在,將大樣本劃分成小樣本並依次解決。
遞迴時間複雜度的估算
只說一種普遍的通用情況
-
master公式
T(N) = A*T(N/B) + O(N^D)
當 量本量為N
的情況下,整個樣本被劃 分成B樣本量
,共 執行A次
。
出去呼叫遞迴過程之外,剩下的部分的時間複雜度 O(N^D)
1) log(b,a) > d -> 複雜度為O(N^log(b,a)) 2) log(b,a) = d -> 複雜度為O(N^d * logN) 3) log(b,a) < d -> 複雜度為O(N^d)
以歸併排序為例:
func mergeProcess(arr: inout [Int] ,left: Int ,right:Int) { if left==right { //左右相等,說明這次要調整的只有一個數 return } let mid = (left+right)/2 //取得中心點位置 mergeProcess(arr: &arr, left: left, right: mid)//T(N/2) mergeProcess(arr: &arr, left: mid+1, right: right)//T(N/2) merge(arr: &arr, left: left, mid: mid, right: right)//O(N) }
有 T(N) = T(N/2) + T(N/2) + O(N) = 2T(N/2) + O(N)
中 A = 2,B=2,D=1
帶入master公式 log(b,a) = 1
,與d值相等。
於是歸併排序的時間複雜度為 O(N * logN)
。