本文只是自己的筆記,並不具備過多的指導意義。
爲了理解很多都使用了遞歸,而不是自己通過while進行壓棧處理。
代碼的初衷是便於理解,網上大神優化過的代碼很多,也不建議在項目中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值相等。