1、首先简述一下原理,其实原理就是我们小学学过的分而治之的思想,先将一个大问题分开成很多小问题,然后再将小问题合起来,组成大问题,那么大问题就被解决了。
那么分治的思想用在数组上面,就体现为:将一个长数组分不断分开,直至每个小数组的长度为二,然后再对每个小数组进行对比排序,从小往大,直至合成大数组。
2、那么对比我们常用的冒泡排序法有什么区别呢?区别在于排序的复杂度降低了,再相同单次算力的情况下,数字比较的次数变少了,从而效率提升了。如下图:
归并排序以程序稳定性为主,因为我们可以提前得知排序的次数是多少(其实是有计算公式的,但是但是网上的由很多版本,这里大家可以自己去查找),但是这样的稳定性带来的副作用是什么呢?就是它的空间复杂度大大提升,以至于再某些环境下,归并排序的速度还不如冒泡排序。
3、算法实现:
先上一个网上找的原理图:
图中比较容易理解的是,“分”部分,就是直接将大问题不断分解成小问题,但是小问题是如何合成大问题的呢?
这里用到的是“对比排序法”。这个算法用文字描述就是:先取出两个数组,以第一个数组的第一个数字为基准,去对比第二个数组,然后将小的数字保存到第三个数组里面。
图片表述如图:
如图,是两个已经已经排序好两个小数组,以第一数组的第一个数字为基准,与第二数组的第一个数字对比,将较小的那个放第三数组的第一位,以此类推。
4、代码实现:
首先,需要将一个大数组拆分开。
let mergeSort = (arrayRan) => { // “分”函数
if (arrayRan.length < 2) {
return arrayRan;
}
let mid = Math.floor(arrayRan.length / 2); //获取数组长度
let arrayHead = arrayRan.slice(0, mid); //获取前半段数组
let arrayEnd = arrayRan.slice(mid); //获取后半段数组
return merge(mergeSort(arrayHead), mergeSort(arrayEnd)); //使用递归,不断将数组拆分,并传递到下一个函数“治”函数。
}
let merge = (arr1, arr2) => { // “治”函数,将接收到“分”函数处理的小数组,并将他们排序,然后有序合成到一个大数组中
let result = [];
// 数组大小排列
while (arr1.length && arr2.length) {
if (arr1[0] <= arr2[0]) {
result.push(arr1.shift());
} else {
result.push(arr2.shift());
}
}
// 合并剩余数组
if (arr1.length > 0) {
result = result.concat(arr1);
}
else {
result = result.concat(arr2);
}
return result; // 返回结果
}
let aaa = mergeSort(arrayRandom); //调用函数
console.log(aaa); //打印结果
到这里,归并排序就完成了,其实代码并不是很复杂,只要理解好递归的部分,这个代码还是很好写出来的。
5、结果分析:其实在第二点中,大家可能留意到我说了这么一句话“以至于再某些环境下,归并排序的速度还不如冒泡排序。”
其实这个是有一定的根据的,如下图所示:
这个是在浏览器中运行的100位乱序数组的归并排序,时间用的是0.322ms,运算次数是298次;
而在冒泡排序中:
同样是100位乱序数组冒泡排序虽然运算次数更多但是用时却少很多,当然基于冒泡排序的空间不稳定性我还特意换了好几个浏览器,重复实验了好几次,发现结果还是一样。
后来猜测一番,我觉得可能是归并排序的多层递归,导致归并程序花费的时间大大提升。
至此,小记一番,也供大家参考。