归并排序(英语:Merge sort,或mergesort),
是创建在归并操作上的一种有效的排序算法,效率为O(nlog n)。
1945年由约翰·冯·诺伊曼首次提出。
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,
且各层分治递归可以同时进行。
解释
以上是 维基百科 关于 归并排序
的描述。其实就是 合并排序
,合并排序
也称 归并排序
。其合并排序的 核心点
就在于:
- 将原数组拆分成若干个子数组,然后通过比较再进行合并
合并
合并
,有 合
才有 并
, 有 分
才有 合
。所以 合并排序
首先要做的流程就是先将原数组 拆分
。原数组 拆分
后,再对其进行 合并
。那 拆分的粒度
是多少呢?或者说 拆成几个
元素?
因为原数组的状态是 无序
的,拆的过程中 无法判断
其 大小顺序
。所以拆的时候,要把 原数组
按照 单个元素
进行 拆分
。拆分之后的每个 子数组中
只有 一个元素
,此时的每个子数组
都可以理解为是 有序状态
。
所以 这时候,问题就 转化
为了:
如何 合并
两个 有序数组
,使之 合并
后的 新数组
状态仍 保持有序
。
首先实现如何合并两个有序数组
比如说,现在有一个 两个有序
数组组成的 无序数组
arr:
const arr = [1, 3, 5, 2, 4, 6];
对于它的前3项[1, 3, 5
] 和后3项([2, 4, 6]
)。都是有序的状态。我们现在要将其合并为一个新数组,合并后的新数组也要保持有序状态。
首先对其进行拆分,因为它是由两个无序数组组成的。所以就根据两个有序数组的交界点对其进行拆分。也就是下标为3的位置
/**
* 合并两个有序数组
* @param {要合并的数组} arr
* @param {左半边开始的位置} p
* @param {左半边结束的位置 右半边开始的位置} q // 要拆分的位置
* @param {右半边结束的位置} r
*/
function merge(arr, p, q, r){
// 首先根据两个有序数组的交界点进行拆分
let arr1 = arr.slice(p, q);
let arr2 = arr.slice(q, r);
// 添加哨兵: 各自向数组尾部添加最大值
// 作用:
// 1 占位 2 减少下方for循环中的判断条件
arr1.push(Number.MAX_SAFE_INTEGER)
arr2.push(Number.MAX_SAFE_INTEGER)
// 循环不变式 对原数组进行回写覆盖
for(let k = p, i = 0, j = 0; k < r; k++){
// p是下标0 初始时k=p=0 且k在不断递增
// i指针默认0 指向arr1中将要写入到原数组中的位置
// j指针默认0 指向arr2中将要写入到原数组中的位置
// 分别比较两个有序数组 arr1 和 arr2 中的每个元素
// 谁小 谁先回写到原数组中
// 注意 每回写一次arr1或者arr2中的元素 要将i或j进行+1
arr[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]
}
}
merge(arr, 0, 3, 5);
// 1,2,3,4,5,6
测试一下即可发现,完成了将两个有序数组合并为一个有序数组。
合并排序的核心实现了,那么接下来代码就比较简单了
JS实现合并排序
/**
* @param {无序数组} arr
* @param {头始指针} p
* @param {尾部指针} r
*/
function merge_sort(arr, p, r) {
if (r - p < 2) return; // 数组中小于两个元素就不需要再拆分了
// 向上取整 计算出要拆分的位置
const q = Math.ceil((p + r) / 2);
// 递归拆分:
// 左
merge_sort(arr, p, q);
// 右
merge_sort(arr, q, r);
// 最后合并:
merge(arr, p, q, r);
}
// 同上
function merge(arr, p, q, r){
let arr1 = arr.slice(p, q);
let arr2 = arr.slice(q, r);
arr1.push(Number.MAX_SAFE_INTEGER)
arr2.push(Number.MAX_SAFE_INTEGER)
for(let k = p, i = 0, j = 0; k < r; k++){
arr[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]
}
}
测试一下
const _arr = [2, 6, 12, 67, 845, 87, 13, 678, 321, 12, 3, 1, 7, 8, 9];
merge_sort(_arr);
console.log(_arr);
/*
[
1, 2, 3, 6, 7, 8,
9, 12, 12, 13, 67, 87,
321, 678, 845
]
*/