聊聊归并排序

122 阅读2分钟

归并排序

归并排序是利用归并思想来完成的排序算法,大致流程如下:

  • 不断将当前序列分割为2个子序列,直到不能再分割为止
  • 不断将2个子序列合并成一个有序序列,直到合并为最后一个序列

大致过程如图所示:

image.png

分析:

  • 不断拆分的过程是一样的,只需要给一个出口那就是同组数据只有一个的时候便不用再拆分了,所以这个过程可以使用递归来完成
  • 在合并过程中因为一直是同处于一个数组当中,那么右边数据在比左边数据小的情况下按道理是要移到左边的,但是就会覆盖住左边的数据,那么最好是要把左边的数据拷贝出来到另外的空间,那么是不是要拷贝整个数组的数据呢?其实并不用,只需要一半的空间即可。
  • 合并分为左边和右边的数据,以mid为中间界限来隔开,那么合并中的逻辑应当是左右两边不停比较,小的放在数组相应的位置,如果左边的数据比较完了右边则不用动,反之如果右边的比较完了左边的应当移到数组后面的位置

再来解释一下为什么临时存放数据的数组只需要有原数组一半的空间即可,因为如图所示,创建临时数组的意义就在于由于是在同一数组中进行排序,一旦右边的数据小于左面,应当把右边的数据移到左边,但是会产生覆盖的问题,那么临时数组只有一半空间是否会导致右边的数据被覆盖,答案是不会,因为看指针Ai和指针Ri天然会有Mid - begin这些间隔,也就是说Ai和指针Ri离的最近的情况就是右边一直比左边大,那么Li指针一直右移直到Le的位置,所以留一半的空间即可,并不会对右边数据产生影响

image.png

代码完成

image.png

测试数据:

image.png

时间复杂度分析

因为算法使用了递归所以并不容易看出来时间复杂度,那么使用归纳法来演示

归并排序花费的时间:

  • T(n)T(n) = 2T(n/2)2*T(n/2) + O(n)O(n)
  • T(1)T(1) = O(1)O(1)
  • T(n)/nT(n)/n = T(n/2)/n/2T(n/2)/n/2 + O(n)/nO(n)/n
  • S(n)S(n) = T(n)/nT(n)/n
  • S(n)S(n) = S(n/2)S(n/2) + O(1)O(1) = S(n/4)S(n/4) + O(2)O(2) = S(n/8)S(n/8) + O(3)O(3) = S(n/2k)S(n/2^k) + O(k)O(k)
  • S(n)S(n) = S(1)S(1) + O(logn)O(logn) = O(logn)O(logn)
  • T(n)T(n) = O(nlogn)O(n*logn)

由于归并排序是平均分割子序列,所以最好、最坏、平均时间复杂度均为O(nlogn)O(n*logn)

归并排序的空间复杂度是O(n/2+logn)O(n/2+logn) = O(n)O(n),前者是用于存放临时数组,后者是因为递归调用

接下来记载一些常见的递推式和对应的复杂度

递推式复杂度
T(n)T(n) = T(n/2)T(n/2) + O(1)O(1)O(logn)O(logn)
T(n)T(n) = T(n1)T(n-1) + O(1)O(1)O(n)O(n)
T(n)T(n) = T(n/2)T(n/2) + O(n)O(n)O(n)O(n)
T(n)T(n) = 2T(n/2)2*T(n/2) + O(1)O(1)O(n)O(n)
T(n)T(n) = 2T(n/2)2*T(n/2) + O(n)O(n)O(nlogn)O(n*logn)
T(n)T(n) = T(n1)T(n-1) + O(n)O(n)O(n2)O(n^2)
T(n)T(n) = 2T(n1)2*T(n-1) + O(1)O(1)O(2n)O(2^n)
T(n)T(n) = 2T(n1)2*T(n-1) + O(n)O(n)O(2n)O(2^n)