[Swift]归并排序 | Merge sort

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11075281.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

1、定义

创建在归并操作上的一种有效的排序算法,效率为。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

分治法

(1)、分割:递归地把当前序列平均分割成两半。

(2)、集成:在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)。

2、算法分析

最坏时间复杂度:

最优时间复杂度:

平均时间复杂度:

最坏空间复杂度:

3、动图展示

(1)、对7个整数值的数组进行归并排序(自上而下)。

(2)、使用归并排序为一列数字进行排序的过程:

(3)、使用归并排序为一列数字进行排序的过程

4、归并操作(merge)

也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。

5、归并操作一:递归法(Top-down)

(1)、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

(2)、设定两个指针,最初位置分别为两个已经排序序列的起始位置

(3)、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

(4)、重复步骤3直到某一指针到达序列尾

(5)、将另一序列剩下的所有元素直接复制到合并序列尾

 1 //MARK:归并排序(Top-down implementation)
 2 func mergeSort(_ array: [Int]) -> [Int] {
 3     //1.如果传入数组为空或者只有一个元素,我们就不需要继续拆分,直接返回该传入数组。
 4     guard array.count > 1 else { return array }
 5     //2.找出数组个数中间值。
 6     let middleIndex = array.count / 2
 7     //3.使用步骤2得到的中间值,不断的拆分左边的数组。
 8     let leftArray = mergeSort(Array(array[0..<middleIndex]))
 9     //4.同时不断拆分右边的数组。
10     let rightArray = mergeSort(Array(array[middleIndex..<array.count]))
11     //5.最后,将所有数组合并到一起,得到排序数组。
12     return merge(leftPile: leftArray, rightPile: rightArray)
13 }
14 15 //MARK:归并方式一:递归法(Top-down)
16 func merge(leftPile: [Int], rightPile: [Int]) -> [Int] {
17     //1.在合并过程中,你需要创建2个索引来追踪合并的两个数组。
18     var leftIndex = 0
19     var rightIndex = 0
20     //2.这里的空数组为合并数组,在接下来的步骤里,这个空数组会不断地从其它数组放入新元素。
21     var orderedPile = [Int]()
22     //3.这里的while循环会将左右两侧的数组进行比较,并按照大小放入到orderedPile里。
23     while leftIndex < leftPile.count && rightIndex < rightPile.count {
24         if leftPile[leftIndex] < rightPile[rightIndex] {
25             orderedPile.append(leftPile[leftIndex])
26             leftIndex += 1
27         } else if leftPile[leftIndex] > rightPile[rightIndex] {
28             orderedPile.append(rightPile[rightIndex])
29             rightIndex += 1
30         } else {
31             orderedPile.append(leftPile[leftIndex])
32             leftIndex += 1
33             orderedPile.append(rightPile[rightIndex])
34             rightIndex += 1
35         }
36     }
37     //4.如果执行到步骤4,则证明leftPile和rightPile里的数字均已完全合并到orderedPile里。这时候,我们不需要继续做比较,只需要将剩下的数组合并起来。
38     while leftIndex < leftPile.count {
39         orderedPile.append(leftPile[leftIndex])
40         leftIndex += 1
41     }
42     
43     while rightIndex < rightPile.count {
44         orderedPile.append(rightPile[rightIndex])
45         rightIndex += 1
46     }
47     
48     return orderedPile
49 }

测试:

1 var arr:[Int] = [1,9,2,8,3,7,4,6,5,10,11,12]
2 print(mergeSort(arr))
3 //Print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

5、归并操作二:迭代法(Bottom-up)

原理如下(假设序列共有个元素):

(1)、将序列每相邻两个数字进行归并操作,形成个序列,排序后每个序(2)、列包含两/一个元素

(3)、若此时序列数不是1个则将上述序列再次归并,形成个序列,每个序列包含四/三个元素

(4)、重复步骤2,直到所有元素排序完毕,即序列数为1

 1 //MARK:归并排序(Bottom-up implementation)
 2 //MARK:归并方式二:迭代法(Bottom-up)
 3 func mergeSort<T>(_ a: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] {
 4     let n = a.count
 5     //1.我们在合并左右两侧的数组时候,不能同时修改它们自身数组的内容,所以我们需要一个临时工作数组来执行这个功能。但是每一次合并过程都重新分配新的临时工作数组是非常浪费资源的,因此,我们使用两个工作数组来代替,并用d来区分它们,数组z[d]是用来读取,z[d-1]用来写,我们称之为双重缓冲。
 6     var z = [a, a]
 7     var d = 0
 8     
 9     var width = 1
10      //2.从概念上来说,这一版本的工作方式和上一个版本的工作方式是相同的。首先,它会合并长度为1的数组,然后是2,然后是4,以此类推。数组的长度是有width决定的。最初的时候它的数值是1,然后在每个循环结束后乘以2。所以外循环决定了需要合并的数组长度,在每一次进入新的循环,需要合并的数组的长度跟着变得更大。
11     while width < n {
12         
13         var i = 0
14           //3.内循环是一个合并过程,合并后的数组会被放入z[1 - d]。
15         while i < n {
16             
17             var j = i
18             var l = i
19             var r = i + width
20             
21             let lmax = min(l + width, n)
22             let rmax = min(r + width, n)
23              //4.从逻辑上来说,此版本与上一个版本相同,唯一不同的是,这里我们使用了双重缓冲,所以我们从z[d]上读数值,在z[1-d]写数组。这里的isOrderedBefore函数可以对比各种类型的数据,不仅仅是数字。
24             while l < lmax && r < rmax {
25                 if isOrderedBefore(z[d][l], z[d][r]) {
26                     z[1 - d][j] = z[d][l]
27                     l += 1
28                 } else {
29                     z[1 - d][j] = z[d][r]
30                     r += 1
31                 }
32                 j += 1
33             }
34             while l < lmax {
35                 z[1 - d][j] = z[d][l]
36                 j += 1
37                 l += 1
38             }
39             while r < rmax {
40                 z[1 - d][j] = z[d][r]
41                 j += 1
42                 r += 1
43             }
44             
45             i += width*2
46         }
47         
48         width *= 2
49         //5.此时,z[d]数组已经合并数组大小为为z[1-d]。交换活动数组,以便在下一步中从刚刚创建的新堆中读取数据。
50         d = 1 - d
51     }
52     return z[d]
53 }

测试:

1 var arr:[Int] = [1,9,2,8,3,7,4,6,5,10,11,12]
2 print(mergeSort(arr,>))
3 //Print [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

原文地址:https://www.cnblogs.com/strengthen/p/11075281.html