1、算法介绍
归并排序(MergeSort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。归并排序的算法时间复杂读为O(NLogN),是一种稳定的排序算法。
2、递归算法的代码实现
/**
* 归并序列辅助函数
* @param {Array} arr 需要被排序的数组
* @param {Number} leftStartPos 当前被合并序列的左索引起始值
* @param {Number} rightStartPos 当前被合并序列的右索起始引值
* @param {Number} rightEndPos 当前被归并序列的右索引结束值
*/
function merge(arr, leftStartPos, rightStartPos, rightEndPos) {
// 记住当前序列在数组序列中的开始位置
var pos = leftStartPos;
// 记住当前两个序列的长度和
var length = rightEndPos - leftStartPos + 1;
// 左序列的结束位置
var leftEndPos = rightStartPos - 1;
// 临时序列,用来保存子序列的合并结果
var newArr = [];
while (leftStartPos <= leftEndPos && rightStartPos <= rightEndPos) {
// 如果A序列的值比B序列的值小,先合并A序列的值,这将会使得数据按从小到大的数据排序
if (arr[leftStartPos] <= arr[rightStartPos]) {
newArr[pos++] = arr[leftStartPos++];
} else {
newArr[pos++] = arr[rightStartPos++];
}
}
// 两个while循环是不会同时成立的,如果A序列比B序列长,直接合并A序列剩下的元素
while (leftStartPos <= leftEndPos) {
newArr[pos++] = arr[leftStartPos++];
}
// 两个while循环是不会同时成立的,如果B序列比A序列长,直接合并B序列剩下的元素
while (rightStartPos <= rightEndPos) {
newArr[pos++] = arr[rightStartPos++];
}
/* 由于两个子序列合并完成时,此刻我们已经不知道左边序列开始位置和右边序列开始位置,
因此,简单的做法是我们可以换一种思考方式,我们将数据从右边复制到左边,
需要拷贝的总长度是两个序列的长度和 */
for (var i = 0; i < length; i++, rightEndPos--) {
arr[rightEndPos] = newArr[rightEndPos];
}
}
/**
* 归并排序的辅助函数
* @param {Array} arr 需要被排序的数组
* @param {Number} left 数组的左起始索引
* @param {Number} right 数组的右结束索引
*/
function _mergeSort(arr, left, right) {
// 如果开始位置和结束位置一样或者比结束位置还要来的大,说明已经没有继续归并的意义了
if (left >= right) {
return;
}
var center = Math.floor((left + right) / 2);
//沿着左序列继续归并
_mergeSort(arr, left, center);
//沿着右序列继续归并
_mergeSort(arr, center + 1, right);
//合并当前已经排序完成的左右序列
merge(arr, left, center + 1, right);
}
/**
* 统一对外接口,为外界保留简单的调用接口
*/
function mergeSort(arr) {
if (!Array.isArray(arr)) {
throw new Error("can not sort");
}
return _mergeSort(arr, 0, arr.length - 1);
}
3、递归算法的排序思路
归并排序的重点思想便是合并两个有序子序列
3.1 合并两个有序序列
假设我们现在有两个有序子序列a: [1,2,8,14,15], b: [6, 7, 10],合并两个子序列我们需要借助三个变量分别记住a序列当前被合并到的位置posA, b序列当前被合并到的位置posB, 还有当前合并到新数组的位置。
1、起始状态
2、比较大小,决定合并哪个序列的值到新序列中
因为A[posA]的值是1,比B[posB]的值来的小,所以,我们需要将A[posA]的值插入到新序列中,并且A序列的指针位置后移(posA++),新序列的位置也需要后移(posC++)
3、合并完成一个序列,剩余序列直接插入新序列中
4、完成合并
3.2 分而治之
3.2.1 Step1
假设我们有如下测试数据:[2, 7, 3, 5, 8, 4, 10, 9, 1] 首先,将我们的数组一分为二,得到两个子序列。
3.2.2 Step2
我们发现左子序列还可以继续以上操作,于是,我们继续以上操作(沿着左边序列递归)。 此刻,我们可以看出已经不能继续二分了(当什么时候便不能继续以上操作了呢,当startPos和endPos相等的时候,说明此刻已经无法继续二分了)此刻,我们得到了的两个序列,[2]和[7],那么我们就可以认为这个序列已经是完成排序的序列。因此可以直接合并。
3.2.3 Step3
此刻我们已经完成部分片段的排序了
3.2.4 Step4
3、总结
归并排序的示例代码是我的一种实现方式,可能和大家实现有一些写法上的区别(文中的所提及的左右序列均是原始数组上的一个片段,通过传入指针的位置确定),各位读者不用拘泥于此,只要掌握其核心原理,您可以尝试实现按自己的思路编码实现,总之,理解第一,切勿囫囵吞枣,死记硬背(如果你无法理解此算法的思想的话,就算背下来了随着时间的推移也会忘记)。
由于笔者水平有限,写作过程中难免出现错误,若有纰漏,请各位读者指正,请联系作者本人,邮箱404189928@qq.com,你们的意见将会帮助我更好的进步。本文乃作者原创,若转载请联系作者本人。