前言
- 归并排序是典型的分治法案例。
分治法是建基于多项分支递归的一种很重要的算法范式。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
——维基百科
-
我的理解:对于一些问题,直接对其进行求解的消耗较大或极为复杂而难以直接求解,而分治法将一个大问题分解问一组小问题,对每个小问题进行分别求解,由小问题的解,最后组成大问题的解。
-
归并排序分为两种,递归实现和迭代实现,本文论述递归思想(迭代法后续可能增加)。
算法分析
算法思想
归并排序在于将需要排序的数组一分为二,二分为四,四分为八…直到数组内部只剩余一个数据,便开始归并重组。归并排序有分也有合,重点在于其合的部分,即归并,将两个已经排序好的数组进行合并。
图解思想:
分数组操作详解:
考虑到数组并非一定为偶数个,将数组划分为两个,第一组为n/2个,第二组为n-n/2个,以此类推。
合数组操作详解:
比较两个待合并数组的第一个元素,将较小的元素先放入新数组中,然后开始下一轮比较,重复操作,直至两个待合并数组有一个数组已经全部比较完毕(即没有数据了),将另外一组还有数据的数组直接添加到新数组尾部进行合并(原理:待合并数组来自于上一次合并后的结果,内部已经有序,所以在当前合并时,剩余的数据即有序,又全部大于新数组的数据,便可直接添加)。
代码分析
具体代码分析
递归函数分析:
// 递归主函数
function Mergesort(arrayA) {
// 0.如果当前数组的数据只剩一个了
if (arrayA.length > 1) {
// 1.新建数组 接收分开的数组结果
var arrayB = new Array();
var arrayC = new Array();
var length = arrayA.length;
// Math.floor js向下取整函数 slice js截取数组函数
// 2.数组复制
arrayB = arrayA.slice(0, Math.floor(length / 2));
arrayC = arrayA.slice(Math.floor(length / 2), length);
// 递归进
// 3.进入递归 进行划分
arrayB = Mergesort(arrayB);
arrayC = Mergesort(arrayC);
// 合并数组
// 4.合并数组
arrayA = Merge(arrayB, arrayC);
}
// 与C语言不同的是 JS需要一个返回值
// 5.返回数组
return arrayA;
}
- 0.只有在数组内数据个数大于1时,才进行划分操作,否则,直接返回当前数组。
- 1.新建两个准备接收的数组B、C,用于接收本次传入数组所划分出来的两个数据。
- 2.将传入数组A的前length/2复制给数组B,length-length/2复制给C。
- 3.将两个已经划分好的数组传入Mergesort函数下一次划分。
- 4.上一次划分已完毕,并且已进行合并后传回两个排序好的数组,将这个两个数组传入合并函数中,并传回一个合并好的数组A。(需要清楚,当前的arrayB合arrayC是什么?是已经归并完成排序好的数组!而不是第2步划分后的数组。)
- 5.返回当前合并好的数组。
合并函数分析:
// 合并函数
function Merge(arrayB, arrayC) {
// 1.准备一个空的合并数组
var arrayA = new Array();
var i = 0,
j = 0,
k = 0,
p = arrayB.length,
q = arrayC.length;
// 2.
while (i < p && j < q) {
// 3.
if (arrayB[i] < arrayC[j]) {
arrayA[k++] = arrayB[i++];
} else {
arrayA[k++] = arrayC[j++];
}
}
// 4.判断哪个函数已经比较完
if (i == p) {
for (var t = j; t < q; t++) {
arrayA[k++] = arrayC[t];
}
} else {
for (var t = i; t < q; t++) {
arrayA[k++] = arrayB[t];
}
}
// 需要返回值
// 5.
return arrayA;
}
- 1.新建一个空数组,用于接收两个数组的合并结果。
- 2.在两个传入数组没有比较完成的情况下,一直比较下去,直到i=p或j=q完成比较。
- 3.将两个数组的第一个数据进行比较,较大的那一个加入新数组中,并且自身加1。
- 4.判断已经全部比较完的数组,将未全部比较的数组直接加入到新数组中。
- 5.返回合并好的数组
数据案例
测试数据:
// 测试数据
var arr = [3, 9, 1, 4, 6, 5, 2, 10, 9]
arr = Mergesort(arr);
console.log(arr);
- [3, 9, 1, 4, 6, 5, 2, 10, 9]
- [ 3, 9, 1, 4 ] [ 6, 5, 2, 10, 9 ]
- [ 3, 9 ] [ 1, 4 ] [ 6, 5 ] [ 2, 10, 9 ]
- [ 3 ] [ 9 ] [ 1 ] [ 4 ] [ 6 ] [ 5 ] [ 2 ] [ 10, 9 ]
- [ 3, 9 ] [ 1, 4 ] [ 5, 6 ] [ 2 ] [ 10 ] [ 9 ]
- [ 1, 3, 4, 9 ] [ 5, 6 ] [ 2 ] [ 9, 10 ]
- [ 1, 3, 4, 9 ] [ 5, 6 ] [ 2, 9, 10 ]
- [ 1, 3, 4, 9 ] [ 2, 5, 6, 9, 10 ]
- [ 1, 2, 3, 4, 5, 6, 9, 9, 10 ]
算法效率分析
不存在最好或者最坏的情况,是一种稳定算法。
T(n)=2T(n/2)+n/2;
时间复杂度有大O表示法:O(nlogn)。
后记
归并排序虽然是一种稳定的排序算法,但当前子问题个数过多时,仍然会造成算法耗时较高,并且其空间复杂度为O(n),会消耗大量内存,不建议使用。如果对内存要求不严,这的确是一种非常好的算法。