什么是分而治之
在学习归并排序之前,需要先了解一下什么是分而治之。
分而治之是算法设计中的一种思想。它将一个问题分成多个和原问题相似的小问题,递归解决小问题, 再将解决方式合并以解决原来的问题。
分而治之算法的三部分:
- 分解原问题为多个子问题(原问题的多个小实例)。
- 解决子问题,用返回解决子问题的方式的递归算法。递归算法的基本情形可以用来解决子问题。
- 组合这些子问题的解决方式,得到原问题的解。
什么是归并排序
归并排序是一种分而治之的算法。
所以,归并排序的基本思想也同上面分而治之算法的三部分一致:
- 将原始数组切分成较小的数组
- 直到每个小数组只有一个位置的大小(也就是把数组长度为1),接着将小数组归并成较大的数组
- 直到最后只有一个排序完毕的大数组
图解归并排序
假设有这样一组数字需要排序。

首先,我们将数字分成两半。

再继续将数字序列对半分割。

再继续分割,直到每个小数组只有一个位置的大小(也就是把数组长度为1),分割完毕。

接下来,对分割后的元素进行合并。(合并假设按照升序排列)
将6与4进行合并,按升序规则,6比4大,先移动4,再移动6,合并后的顺序为[4,6]

接下来把3和7进行合并,3比7小,可以直接合并,合并后的顺序为[3,7]

此时,已经产生了两组从小到大排列的数据[4,6]和[3,7],符合了归并的要求,将这两组数据代入归并中,进行合并。
由于他们两个数据均是包含多个数字的组,则从开头的数字开始比较。
在图中,比较开头的4和3,4大于3,所以移动3。

再从开头比较剩余的数字,在图中,比较4和7。
4小于7,所以移动4。

比较6和7,6小于7,移动6。

移动剩下的7。

此时右边还有未递归合并的组,也像上方相同的操作对数字进行合并。

再继续对右边子数组进行合并。

同样进行合并,直到所有数字都在一个组中,此时归并排序完成。

动画图解归并排序

图片解析归并排序

排序思路
由于是分治法,归并排序也是递归的。我们要将算法分为两个函数:
第一个:将一个大数组分为多个小数组并调用用来排序的辅助函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
export function mergeSort(array, compareFn = defaultCompare) { if (array.length > 1) { const { length } = array; const middle = Math.floor(length / 2); const left = mergeSort(array.slice(0, middle), compareFn); const right = mergeSort(array.slice(middle, length), compareFn); array = merge(left, right, compareFn); } return array; }
|
第二个:归并函数,负责合并和排序小数组来产生大数组,直到回到原始数组并已排序完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
function merge(left, right, compareFn) { let i = 0; let j = 0; const result = []; while (i < left.length && j < right.length) { result.push(compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++]); } return result.concat(i < left.length ? left.slice(i) : right.slice(j)); }
|
完整代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| export const Compare = { LESS_THAN: -1, BIGGER_THAN: 1, EQUALS: 0 };
export function defaultCompare(a, b) { if (a === b) { return Compare.EQUALS; } return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; }
function merge(left, right, compareFn) { let i = 0; let j = 0; const result = []; while (i < left.length && j < right.length) { result.push(compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++]); } return result.concat(i < left.length ? left.slice(i) : right.slice(j)); }
export function mergeSort(array, compareFn = defaultCompare) { if (array.length > 1) { const { length } = array; const middle = Math.floor(length / 2); const left = mergeSort(array.slice(0, middle), compareFn); const right = mergeSort(array.slice(middle, length), compareFn); array = merge(left, right, compareFn); } return array; }
|