说真的,看着《算法导论》有关 归并排序 的伪代码,我的内心是崩溃的。最坑的地方在于,书中的伪代码,数组下标,1 ~ arr.count 。而真实数组的下标,0 ~ (arr.count - 1)。这其中有好多天坑,经常绕不过来这个弯,各种数组越界。其实,还是太浮躁,上手就想改伪代码,实现算法。如果真的先去老老实实理解这个算法,理解它每步的含义,是不会出现这问题的。我昨晚花了一晚时间未果,今早老老实实去理解了下代码,一次成功。
var arr: [Int] = [2, 9, 6, 9, 9, 5, 1, 7, 5]
//辅助过程函数merge,用于完成合并。其中 p, q, r 是数组的下标
func merge(arr: inout [Int], p: Int, q: Int, r: Int) {
// n1 代表了数组下标 p ~ q 的元素的数量,但是少了q本身,需要 +1
let n1 = q - p + 1
// n2 代表了数组下标 (q + 1) ~ r 的元素的数量
let n2 = r - q
var L: [Int] = [], R: [Int] = []
// L存储了 p ~ q 的元素
for i in 0..<n1 {
L.append(arr[p + i])
}
// R存储了 (q + 1) ~ r 的元素
for j in 0..<n2 {
R.append(arr[(q + 1) + j])
}
// 哨兵值,不可能为最小值。当两个数组都露出哨兵值,表示循环结束。
L.append(Int.max)
R.append(Int.max)
// 数组 L 和 R 的计数器
var i = 0, j = 0
for k in p...r {
if L[i] <= R[j] {
arr[k] = L[i]
i = i + 1
} else {
arr[k] = R[j]
j = j + 1
}
}
}
func mergeSort(_ arr: inout [Int], p: Int, r: Int) {
if p < r {
let q: Int = (p + r) / 2
mergeSort(&arr, p: p, r: q)
mergeSort(&arr, p: (q + 1), r: r)
merge(arr: &arr, p: p, q: q, r: r)
}
}
mergeSort(&arr, p: 0, r: (arr.count - 1))
print(arr)
和网上所查找的大多数归并排序不同,在merge方法中,多了一些细微的变化。为了避免在每个基本步骤必须检测L数组和R数组是否为空,我们在每个数组底部放置一个哨兵值,这个哨兵值采用Int类型的最大值。当数组显露出哨兵值时,它不可能为较小的值,除非两个数组都显露出了哨兵值。一旦发生这种情况,所有非哨兵值的元素都已被放入到输出数组,算法停止。