归并排序
算法介绍
归并排序,它有两大核心操作.
一个是将数组一分为二,一个无序的数组成为两个数组.
另外一个操作就是,合二为一,将两个有序数组合并成为一个有序数组.
学习归并排序最为核心的就是需要掌握分而治之的思想.
算法过程
-
找到中心点
-
根据中心将当前数组分成俩个部分, 递归排序俩个部分
-
拿到俩个排好序的数组, 对俩个排好序的数组进行合并
代码实现
const tmp = [];
function merge_sort(l, r, nums) {
if(l >= r) return ; // 边界处理, 当区间为1时直接返回
// 1. 获取中心点坐标
let mid = l + r >> 1;
// 2。递归排序俩个区间
merge_sort(l, mid), merge_sort(mid + 1, r);
// 3. 合并俩个排好序的数组
let i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r) {
if(nums[i] <= nums[j]) tmp[k ++] = nums[i ++];
else tmp[k ++] = nums[j ++];
}
while(i <= mid) tmp[k ++] = nums[i ++]; // 左区间还剩余元素
while(j <= r) tmp[k ++] = nums[j ++]; // 右区间还剩余元素
// 将临时数组中排好序的元素放回到nums的l ~ r区间中
for(let i = l, j = 0; i <= r; i ++, j ++) nums[i] = tmp[j];
}
代码讲解
- 为什么要在全局申明一个tmp数组
tmp数组也可以申明在局部, 但这样每次递归就会多创建一个变量, 递归结束后变成垃圾数据需要回收. 因为每次都是覆盖tmp的区间, 上次存放在tmp中的数据不会对本次造成影响, 所以可以放在全局可以进行复用.
- while循环这么多, 会不会造成性能影响
while(i <= mid && j <= r) {
if(nums[i] <= nums[j]) tmp[k ++] = nums[i ++];
else tmp[k ++] = nums[j ++];
}
while(i <= mid) tmp[k ++] = nums[i ++];
while(j <= r) tmp[k ++] = nums[j ++];
这三个while循环是为了做一件事情, 将nums[l ~ mid]和nums[mid + 1 ~ r]俩区间的数字按照顺序放在tmp区间中. W 在第一个while中, 每次都会取出nums[i]和nums[j]的最小值放在tmp数组中. 因为nums[l ~ mid]和nums[mid + 1 ~ r]是排好序的区间, 所以若nums[i]小于nums[j], 那就证明nums[i]是俩个区间的最小值, 反之是nums[j].
第二和第三个while循环只会有一个会触发, 因为当触发是要不是i > mid, 就是j > r. 这俩个while循环就是为了将剩余的没有放进tmp数组中的元素放到tmp数组中.
这整个过程实际上是让俩个指针遍历俩个区间, 时间复杂度是O(第一个区间长度 + 第二个区间长度)
复杂度分析
时间复杂度
归并排序的时间复杂度十分稳定, 就是O(NlogN). 每次都可以将数组平均拆分成俩半(分), 合并的时候遍历数组中的每个元素(并), 每层需要计算N次, 总共logN层, 所以时间复杂度是O(NlogN)的
空间复杂度
我们需要一个额外的数组空间临时存放排序的数组元素, 所以额外空间复杂度是O(N)
课后作业
模板题
AC代码
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
quick_sort(0, nums.length - 1);
return nums;
function quick_sort(l, r) {
if(l >= r) return ;
let x = nums[l + r >> 1],
i = l - 1,
j = r + 1;
while(i < j) {
do i ++; while(nums[i] < x);
do j --; while(nums[j] > x);
if(i < j) [nums[i], nums[j]] = [nums[j], nums[i]];
}
quick_sort(l, j), quick_sort(j + 1, r);
}
};
本文正在参与「掘金 3 月闯关活动」,点击查看活动详情