JavaScript实现归并排序算法并详解

归并排序是非常经典的基础排序之一,使用分治的思想,分而治之。

先将待排序数组分拆为更小的数组,一直拆分到只有一个元素(只有一个元素的数组就是有序的),然后对分拆的数组按顺序进行合并。这个思想就是分而治之的思想,变成实现的时候,常用递归编程技巧来实现

直接上图来说这个过程(图是拷贝网上的):

 这里重点详细讲一下合并过程,合并被分解好的左右两部分的时候,左右两部分分别已经排好了,因此可以按如下图策略合并:比如需要合并left [1,5,6]的数组和right [2,3,4]数组的过程图:

 

整体的思路看完了,准备用递归实现,递归实现最重要的是写出递归公式、终止条件,地推公式可以这样写:

sort(arr) = merge(mergeSort(arr1), mergeSort(arr2)); 

在数组只有一个元素的时候,就不用再排序了。

撸代码,注意,要重点看一下merge的实现,有一点技巧:

function mergeSort(arr){
    // 拆到只有一个元素的时候,就不用再拆了。
    if(arr.length <2){
        return arr;
    }
    const mid = arr.length/2;
    const left = arr.slice(0, mid);
    const right = arr.slice(mid);
    const leftSplited = mergeSort(left);
    const rightSplited = mergeSort(right);
    return merge(leftSplited, rightSplited);
}

 merge的实现:

// 合并做分支数组和右分支数组,左分支和右分支在自己数组里面已经是有序的。所以就方便很多。
function merge(left, right){
    let tmp = [];
    let leftIndex =0, rightIndex = 0;
    
    while(leftIndex < left.length && rightIndex < right.length) {
        if(left[leftIndex] <= right[rightIndex]) {
            tmp.push(left[leftIndex]);
            leftIndex ++;
        }else{
            tmp.push(right[rightIndex]);
            rightIndex ++;
        }
    }
    // 最后,检查一下是否有数组内容没有拷贝完
    if(leftIndex < left.length){
        tmp = tmp.concat(left.slice(leftIndex))
    }
    if(rightIndex < right.length){
        tmp = tmp.concat(right.slice(rightIndex))
    }
    return tmp;
}

这个实现可以进一步优化,如果说左右数组已经是有大小关系的了,比如,左边数组的最后一个元素比右边的第一个小,那么,就不用while循环了,就直接合并就行。

因此可以优化为:

// 合并做分支数组和右分支数组,左分支和右分支在自己数组里面已经是有序的。所以就方便很多。
function merge(left, right){
    // 剪枝优化
    if(left[left.length -1] <= right[0]){
        return left.concat(right)
    }
    if(right[right.length-1] <= left[0]){
        return right.concat(left)
    }

    let tmp = [];
    let leftIndex =0, rightIndex = 0;
    
    while(leftIndex < left.length && rightIndex < right.length) {
        if(left[leftIndex] <= right[rightIndex]) {
            tmp.push(left[leftIndex]);
            leftIndex ++;
        }else{
            tmp.push(right[rightIndex]);
            rightIndex ++;
        }
    }
    // 最后,检查一下是否有数组内容没有拷贝完
    if(leftIndex < left.length){
        tmp = tmp.concat(left.slice(leftIndex))
    }
    if(rightIndex < right.length){
        tmp = tmp.concat(right.slice(rightIndex))
    }
    return tmp;
}

注,关于Merg函数,网上有不少的示例感觉都是有些问题的。比如:

作为对比学习,大家可以先看下这个代码的问题在哪里?

// https://www.cnblogs.com/sunmarvell/p/9248676.html
function merge(left,right){
	var temp=[];
	while(left.length&&right.length){
		if(left[0]<right[0]){
			temp.push(left.shift());
		}else{
			temp.push(right.shift());
		}
	}
	//left和right长度不一样时,直接连接剩下的长的部分(本身有序)
	return temp.concat(left,right);

}

这个代码功能上完全没有问题。但是性能上会有问题。while循环每次运行的时候,都会删除数组的第一个元素。删除数组的第一个元素,是会有导致底层需要进行数据搬移,以维持数据的连续性。搬移一次数组的时间复杂度就是T(n) = O(n);事实上,会让merge函数的时间复杂度提升一个量级,从T(n) = O(n)变为T(n) = O(n)*O(n)。

JS在进行算法处理的时候,要慎用原生提供的修改原数组功能的函数,比如shift、splice等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值