跳到主要内容

归并排序

2024年04月03日
柏拉文
越努力,越幸运

一、认识


归并排序英文称为Merge Sort,归并排序是建立在归并操作上的一种有效的排序算法。归并排序有两种实现方法:自底向上和自顶向下。

特点:

  • 稳定排序: 如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面
  • 非原地排序: 需要利用额外的数组来辅助排序。
  • 排序方式: Out-place (占用额外内存)
  • 时间复杂度: O(nlognlog_{}{n}) [n 代表数据规模及数据量大小]
  • 空间复杂度: O(n)

扩展:

  • 排序算法的稳定性:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。

那么排序算法的稳定性有什么意义呢?其实它只在一种情况下有意义:当要排序的内容是一个对象的多个属性,且其原本的顺序存在意义时,如果我们需要在二次排序后保持原有排序的意义,就需要使用到稳定性的算法。

二、思想


归并排序思想:

将 1 个数字组成的有序数组合并成一个包含 2 个数字的有序数组,再将 2 个数字组成的有序数组合并成包含 4 个数字的有序数组...直到整个数组排序完成

2.1 自上而下

自上而下的方法是采用分治法思想,具体排序过程分成三个过程:

  • 分解:将当前区间一分为二,即求分裂点 middle = (start + end)/2;

  • 求解:递归地对两个子区间 array[start,mid]array[mid+1,end] 进行归并排序;递归的终结条件:子区间长度为 1(一个记录自然有序)。

  • 合并:将已排序的两个子区间R[start,middle]R[mid + 1..high]归并为一个有序的区间 array[low..high]

2.2 自下而上

自底向上方法,也就是常说的二路归并排序,其基本思想是:第 1 趟排序将长度为 n 的待排序记录看作 n 个长度为 1 的有序子序列,然后将这些子序列两两合并。完成第 1 趟排序之后,将得到 lgn 个长度为 2 的有序子序列(如果 n 为奇数,则最后还有一个长度为 1 的子序列)。第 2 趟排序是在第 1 趟的排序的基础上,将这 lgn 个长度为 2 的子序列两两合并。如此反复,直到最后得到一个长度为n的有序文件为止。从这个排序过程来看,二路归并排序是从将长度为 1 的子序列排序变化到长度为 n 的有序序列,因而是自底向上的。

三、实现


3.1 递归版 (自顶向下)

function merge(array, start, middle, end) {
let left = start;
let right = middle + 1;
let temp = [];
let t = 0;
while (left <= middle && right <= end) {
if (array[left] <= array[right]) {
temp[t++] = array[left++];
} else {
temp[t++] = array[right++];
}
}
while (left <= middle) {
temp[t++] = array[left++];
}
while (right <= end) {
temp[t++] = array[right++];
}
for (let i = 0; i < temp.length; i++) {
array[start++] = temp[i];
}
}

function mergeSort(array, start, end) {
if (start >= end) {
return;
}
let middle = (start + end) >> 1;
mergeSort(array, start, middle);
mergeSort(array, middle + 1, end);
merge(array, start, middle, end);
}

function sort(array) {
mergeSort(array, 0, array.length - 1);
}

const array = [3,2,1,5,4];
sort(array);
console.log(array);

3.2 迭代版(自底向上)

function merge(array, start, middle, end) {
let left = start;
let right = middle + 1;
let temp = [];
let t = 0;
while (left <= middle && right <= end) {
if (array[left] <= array[right]) {
temp[t++] = array[left++];
} else {
temp[t++] = array[right++];
}
}
while (left <= middle) {
temp[t++] = array[left++];
}
while (right <= end) {
temp[t++] = array[right++];
}
for (let i = 0; i < temp.length; i++) {
array[start++] = temp[i];
}
}

function mergeSort(array) {
let range = 1;
while (range < array.length) {
let i = 0;
for (i = 0; i < array.length - 2 * range; i += 2 * range) {
merge(array, i, i + range - 1, i + 2 * range - 1);
}
if (i + range < array.length) {
merge(array, i, i + range - 1, array.length - 1);
}
range *= 2;
}
}

function sort(array) {
mergeSort(array);
}