JavaScript实现排序算法(3)——归并排序

归并排序

归并排序是分治思想的典型应用,先分再合

1 算法过程

(从小到大排序)
分治:
1. 将原数列对半分为两个子数列,分别对两个子数列进行排序;
2. 对子数列进行排序时,重复第一步;
合并:
1. 申请空间,大小为两个`已排序的`子数列之和,该空间用来存放合并后的数列;
2. 设定两个指针,最初位置分别为两个子数列的起始位置;
3. 比较两个指针所指向的元素,选择较小的元素放入第一步申请的空间,并移动该指针到下一位;
4. 重复第三步,直至有一个子数列的指针超出数列尾;
5. 将另一个子数列的元素直接添加到合并数列的尾部;

2 排序演示

对下面的数列进行归并排序

49, 38, 65, 97, 76, 13, 27

将原数列对半分为两个子数列[49, 38, 65][97, 76, 13, 27],分别对两个子数列进行归并排序

49, 38, 65, | 97, 76, 13, 27

重复上面的分割步骤,可以得到

              |
49, | 38, 65, | 97, 76, | 13, 27

继续往下分,得到

    |           |           |
49, | 38, | 65, | 97, | 76, | 13, | 27
                |

此时每个子数列都只有一个元素,开始合并,合并时确保有序,第一次合并后得到

              |
49, | 38, 65, | 76, 97, | 13, 27

第二次合并后得到

38, 49, 65, | 13, 27, 76, 97

第三次合并后得到

13, 27, 38, 49, 65, 76, 97

即为排序后的数组.

3 代码实现

const arr = [3, 4, 6, 11, 9, 5, 7, 8, 1, 2, 10];

// 归并排序主函数
function mergeSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 如果待排数组为空数组或只有一个元素,直接返回原数组
  if (len <= 1) return arr;
  // 选取对半分的分界值索引
  const middle = Math.floor(len / 2);
  // 拿到左右两个子数组
  const left = arr.slice(0, middle);
  const right = arr.slice(middle);
  // 递归 对子数组进行归并排序 最后将两个子数组合并
  return merge(mergeSort(left), mergeSort(right));
}

// 合并方法
function merge(left, right) {
  // 申请新空间,存放排序后的数组
  const res = [];
  // 两个子数组都有元素时,通过比较出较小的数存放入res
  while (left.length && right.length) {
    if (left[0] <= right[0]) {
      res.push(left.shift());
    } else {
      res.push(right.shift());
    }
  }
  // 两个子数组有任一数组元素为空时,将另一数组直接添加至res末尾
  while (left.length) {
    res.push(left.shift());
  }
  while (right.length) {
    res.push(right.shift());
  }
  return res;
}

4 时间复杂度

来推导一下归并排序的时间复杂度

参考自:https://blog.csdn.net/liangjiabao5555/article/details/89670082,在这篇博客的基础上进行了修改(因为我认为最后那里他写的不对)

归并排序总时间 = 划分子数列时间 + 子数列归并排序时间 + 合并子数列时间

由于划分子数列都是对半划分,划分时间为常数,可忽略不计,因此

归并排序总时间 = 子数列归并排序时间 + 合并子数列时间

假设一个含有n项的数列,归并排序所需时间为T(n),那么,将该数列划分为两个子数列,可得

一次划分

T(n) = 2*T(n/2) + 合并时间

由于合并时,两个子数列经过归并排序后,是有序的,因此合并时时间复杂度为n,到这里可得

T(n) = 2*T(n/2) + n  (式子1)

现在来考虑子数列的归并排序,将两个n/2的子数列分为四个n/4的数列

二次划分

T(n/2).很简单,由于是递归定义,只要将(n/2)带入上面的式子1,可得

T(n/2) = 2*T(n/4) + n/2  (式子2)

式子2代入式子1,可得

T(n) = 2 * (2*T(n/4) + n/2) + n
     = 4*T(n/4) + 2n  (式子3)

同理,将四个n/4的子数列可以分为八个n/8的数列

三次划分

T(n/4),将(n/4)代入式子1可得

T(n/4) = 2*T(n/8) + n/4  (式子4)

式子4代入式子3,可得

T(n) = 4 * (2*T(n/8) + n/4)  + 2n
     = 8*T(n/8) + 3n

至此,我们得到了这样的一个结果

二叉树

总结规律,划分到最后,子数列的元素都只有一个时,此时共有n组子数列

最后一次划分

T(n) = n * T(n/n) + nlogn
     = n * T(1) + nlogn

由于最后一次划分,每组子数列只有一个元素,不需要进行组内排序,时间复杂度为0,因此

T(n/n) = T(1) = 0;

可得

T(n) = nlogn

综上,归并排序的时间复杂度为O(nlogn).

5 稳定性

归并排序是稳定的排序,这个很好理解.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值