原理介绍
归并排序是一种基于分治策略的排序算法,它的主要思想是将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成一个有序的序列。
具体来说,归并排序的过程可以分为两个步骤:
- 分:将待排序的序列平均分成两部分,然后对每个子序列分别进行排序,直到每个子序列只有一个元素为止。
- 合:将两个已经有序的子序列合并成一个有序的序列,直到所有子序列都被合并成一个完整的序列为止。
归并排序的时间复杂度为O(nlogn),其中n为待排序序列的长度。它是一种稳定排序算法,因为在排序过程中相同的元素不会改变它们的相对位置。
归并排序虽然在时间和空间上的开销较大,但它的稳定性和高效性使其成为一种常用的排序算法,特别是在对大规模数据进行排序时。
实现方式
自顶向下和自底向上
可以通过自顶向下(top-down)或自底向上(bottom-up)的两种方式实现归并排序。
自顶向下 (top-down):从输入数组出发,不断二分该数组,直到数组长度为1,再执行合并。适合用 递归 实现。
自底向上 (bottom-up):从输入数组的单个元素出发,一一合并,二二合并,四四合并直到数组有序。适合用 迭代 实现。
使用手摇算法实现原地排序
前述归并排序,每一次合并都是将两部分待合并数组的比较结果写入一个与 arr 等大小的临时数组 tmpArr 中,写入后再将 tmpArr 中的合并结果写回到 arr 中。于是 tmpArr 的空间开销即为该实现的空间复杂度,为 O(n)。通过一种 原地旋转交换 的方法(俗称手摇算法/内存反转算法/三重反转算法,英文社区一般称为 block swap algorithm),则只需要 O(1) 的辅助空间(由于递归空间为 O(logn),其总的空间复杂度仍为 O(logn))。以下介绍旋转交换的实现方法。
以 456123 为例,欲将 456 和 123 交换位置转换为 123456,只需要执行三次旋转即可:
- 旋转 456,得到 654
- 旋转 123,得到 321
- 旋转 654321 得到 123456
应用上述「手摇算法」对两个排序序列的「原地归并」过程如下。
- 记左数组第一个数下标为 i ,记右数组第一个数下标为 j 。
- i 向后遍历,找到左数组中第一个 大于 右数组第一个数字 (即 arr[j] ) 的数,此时 arr[i]>arr[j] 。
- 以 index 暂存右数组第一个元素的下标 index=j 。
- 找到右数组中第一个 大于等于 arr[i] 的数,记其下标为 j 。此时必有 [i,index−1] 下标范围序列 大于 [index,j−1] 下标范围序列。
- 通过三次翻转交换 [i,index−1] 和 [index,j−1] 序列 (指下标范围),即依次翻转 [i,index−1] ,翻转 [index,j−1] ,翻转 [i,j−1] 。
- 重复上述过程直到不满足 (i < j && j <= r)
※ 第4步如果找「大于」而不是「大于等于」,对于数字数组排序,结果正确,但将 破坏稳定性。