- 冒泡排序、插入排序、选择排序时间复杂度都是 O(n^2),适合小规模数据的排序。
- 归并排序和快速排序的时间复杂度为 O(nlogn)。适合大规模的数据排序。
- 归并排序和快速排序都用到了分治思想(将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之)
一、🐶 归并排序的原理
- 归并排序使用的就是分治思想,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。
- 分治思想跟递归思想很像。分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧。
- 分解:将数组递归地分解成两个较小的子数组,直到子数组的等于1。此时,每个子数组都被视为“已排序”。
- 递归进行排序并合并:递归地对子数组进行排序,并将已排序的子数组合并成一个大的有序数组,直到合并为1个完整的数组。
二、🐶 归并排序的递推公式和结束条件
// 结束条件
if (arr.length = 1) {
return arr;
}
// 递推公式
// merge_sort(l…r) 表示,给下标从 l 到 r 之间的数组排序。我们将这个排序问题转化为2个子问题,merge_sort左边和merge_sort右边,等到merge_sort左边和merge_sort右边排好序之后我们再把2个有序数组合并在一起。
merge_sort(l…r) = merge(merge_sort(l…mid), merge_sort(mid+1…r))
三、🐶 归并排序的js代码
function mergeSort(arr) {
if (arr.length = 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
// leftIndex和rightIndex2个游标分别指向左🈶右数组的第一个元素
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] <= right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
// 如果左侧数组还有剩余元素,直接添加到结果中
while (leftIndex < left.length) {
result.push(left[leftIndex]);
leftIndex++;
}
// 如果右侧数组还有剩余元素,直接添加到结果中
while (rightIndex < right.length) {
result.push(right[rightIndex]);
rightIndex++;
}
return result;
}
// 使用示例
let arr = [5, 3, 8, 4, 2, 7, 1, 6];
let sortedArr = mergeSort(arr);
console.log(sortedArr); // 输出: [1, 2, 3, 4, 5, 6, 7, 8]
四、🐶 归并排序的性能分析
归并排序是稳定的排序算法吗?
上面代码第20行,当左侧代码等于右侧的时候,我们先push左侧的代码到临时数组result,这样就保证了值相同的元素,在合并前后的先后顺序不变。所以,归并排序是一个稳定的排序算法。
归并排序的时间复杂度是多少?
归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)。
归并排序的空间复杂度是多少?
归并排序并没有像快排那样,应用广泛,这是为什么呢?因为它有一个致命的“弱点”,那就是归并排序不是原地排序算法。这是因为归并排序的合并函数,在合并两个有序数组为一个有序数组时,需要借助额外的存储空间。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)。