归并排序 | 青训营笔记

334 阅读3分钟

原理介绍

归并排序是一种基于分治策略的排序算法,它的主要思想是将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成一个有序的序列。

具体来说,归并排序的过程可以分为两个步骤:

  1. 分:将待排序的序列平均分成两部分,然后对每个子序列分别进行排序,直到每个子序列只有一个元素为止。
  2. 合:将两个已经有序的子序列合并成一个有序的序列,直到所有子序列都被合并成一个完整的序列为止。

归并排序的时间复杂度为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,只需要执行三次旋转即可:

  1. 旋转 456,得到 654
  2. 旋转 123,得到 321
  3. 旋转 654321 得到 123456

应用上述「手摇算法」对两个排序序列的「原地归并」过程如下。

  1. 记左数组第一个数下标为 i ,记右数组第一个数下标为 j 。
  2. i 向后遍历,找到左数组中第一个 大于 右数组第一个数字 (即 arr[j] ) 的数,此时 arr[i]>arr[j] 。
  3. 以 index 暂存右数组第一个元素的下标 index=j 。
  4. 找到右数组中第一个 大于等于 arr[i] 的数,记其下标为 j 。此时必有 [i,index−1] 下标范围序列 大于 [index,j−1] 下标范围序列。
  5. 通过三次翻转交换 [i,index−1] 和 [index,j−1] 序列 (指下标范围),即依次翻转 [i,index−1] ,翻转 [index,j−1] ,翻转 [i,j−1] 。
  6. 重复上述过程直到不满足 (i < j && j <= r)

※ 第4步如果找「大于」而不是「大于等于」,对于数字数组排序,结果正确,但将 破坏稳定性。