- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
目录
- 归并排序
一 归并排序
归并排序
的时间复杂度
为 线性对数阶
O(nlogN)
javaScript中的
Array.prototype.sort
方法没有规定使用哪种排序算法,允许浏览器自定义,FireFox使用的是归并排序法
,而Chrome使用的是快速排序法
归并排序的核心思想
是分治,分治
是通过递归
地将问题分解成相同或者类型相关的两个或者多个子问题
,直到问题简单到足以解决,然后将子问题的解决方案结合起来,解决原始方案的一种思想。
归并排序
通过将复杂的数组分解成足够小的数组
(只包含一个元素),然后通过合并两个有序数组
(单元素数组可认为是有序数组)来达到综合子问题解决方案
的目的。所以归并排序的核心
在于如何整合两个有序数组
,拆分数组只是一个辅助过程
示例:
// 假设有以下数组,对其进行归并排序使其按从小到大的顺序排列:
var arr = [8,7,6,5];
// 对其进行分解,得到两个数组:
[8,7]和[6,5]
// 然后继续进行分解,分别再得到两个数组,直到数组只包含一个元素:
[8]、[7]、[6]、[5]
// 开始合并数组,得到以下两个数组:
[7,8]和[5,6]
// 继续合并,得到
[5,6,7,8]
// 排序完成
JavaScript实现(从小到大排序):
function mergeSort(arr) {
//let count = 0;
console.log(main(arr));
//return count;
function main(arr) {
// 记得添加判断,防止无穷递归导致callstack溢出,此外也是将数组进行分解的终止条件。
if(arr.length === 1) return arr;
// 从中间开始分解,并构造左边数组和右边数组。
// let mid1 = Math.floor(arr.length/2);
// let mid2 = arr.length >> 1;
let mid = arr.length >>> 1;
//console.log('mid2:',mid2,'mid1:',mid1,'mid:',mid);
let left = arr.slice(0, mid);
let right = arr.slice(mid);
console.log('left:',left,'right:',right);
// 开始递归调用。
return merge(arguments.callee(left), arguments.callee(right));
}
// 数组的合并函数,left是左边的有序数组,right是右边的有序数组。
function merge(left, right) {
// il是左边数组的一个指针,rl是右边数组的一个指针。
let il = 0,
rl = 0,
result = [];
// 同时遍历左右两个数组,直到有一个指针超出范围。
while(il < left.length && rl < right.length) {
//count++;
// 左边数组的当前项如果小于右边数组的当前项,那么将左边数组的当前项推入result,反之亦然,同时将推入过的指针右移。
if(left[il] < right[rl]) {
result.push(left[il++]);
}
else {
result.push(right[rl++]);
}
}
// 记得要将未读完的数组的多余部分读到result。
return result.concat(left.slice(il)).concat(right.slice(rl));
}
}
var arr = [3,5,1,6,2];
mergeSort(arr);
// [1, 2, 3, 5, 6]
注意是
因为数组被分解成为了只有一个元素的许多子数组
,所以merge函数从单个元素的数组开始合并,当合并的数组的元素个数超过1时,即为有序数组,仍然还可以继续使用merge函数进行合并。
归并排序的性能确实达到了应用级别,但是还是有些不足,因为这里的merge函数新建了一个result数组来盛放合并后的数组,
导致空间复杂度增加
,这里还可以进行优化,使得数组进行原地排序
。
参考
总结
归并排序的核心思想
是分治
,就是通过递归
地将问题分解成相同或者类型相关的两个或者多个子问题
,直到问题简单到足以解决.归并排序
的时间复杂度
为线性对数阶``O(nlogN)
N>>>1
就代表N的二进制右移一位
,二进制右移一位就能得到中间值
10>>>1 是5
10>>1 也是5
(此中间值在遇到奇数时
,除以2
后向下取整
) 相当于Math.floor(11/2)