循序渐进写一个归并排序

归并排序算是一个比较简单的排序了,它没有涉及到一些复杂的数据结构。

什么是归并

归并就是把两个有序的数组合并成一个有序的新数组,并且这两个数组和生成的新数组必须同时为升序或降序

let arr1 = [1,3,6,8]
let arr2 = [3,5,10]

合并的规程:

  1. 两个数组的首位进行比较,小的哪一个就从对应的数组里shift(),然后把这个值push到新数组的末尾
  2. 重复上面的步骤,直到有一个数组为空了,那么就把另一个数组的剩余部分全部push到新数组里,结束。

代码实现:

function merge(arr1, arr2) {
    let result = []

    while (true) { 
        //检查两个数组那个空了
        if (arr1.length == 0) {
            result.push(...arr2)
            break
        }
        if (arr2.length == 0) {
            result.push(...arr1)
            break
        }

        // 两个数组首位进行比较,小的被取出,并添加到新数组的末尾
        if (arr1[0]<arr2[0]) {
            result.push(arr1.shift())
        }else{
            result.push(arr2.shift())
        }
    }
    return result
}

什么是归并排序  

我们可以把一个数组里的每两个元素看作是两个数组,然后两两合并

let arr = [4,3,6,7,8,9,9,0]
let arr = [[4],[3],[6],[7],[8],[9],[9],[0]]

把arr数组两个两个传入merge函数 ,得到一个新数组

let newArr1 = [[3,4],[6,7],[8,9],[0,9]]

 把newArr1数组两个两个传入merge函数 ,得到一个新数组

let newArr2 = [[3,4,6,7],[0,8,9,9]]

  把newArr2数组两个两个传入merge函数 ,得到一个新数组

let newArr2 = [[0,3,4,6,7,8,9,9]]

然后取出newArr2[0]就是我们要的排序后的数组了。

来看看代码的实现:

// 从下向上归并,非递归实现
// 先实现一个合并两个有序数组的merge函数
function merge(arr1, arr2) {
    let newArr = []
    // 因为两个参数传进来必须是数组,但是第一轮合并传进来的肯定是一个number
    //所以要用一个Array.of()来把一个number包装成[number]
    if (!Array.isArray(arr1)) {
        arr1 = Array.of(arr1)
    }
    if (!Array.isArray(arr2)) {
        arr2 = Array.of(arr2)
    }
    while (true) {
        if (arr1.length<=0) {
            newArr.push(...arr2)
            return newArr
        }
        if (arr2.length<=0) {
            newArr.push(...arr1)
            return newArr
        }
        let temp = arr1[0]<arr2[0] ? arr1.shift() : arr2.shift()
        newArr.push(temp)
    }
}

function mergeSort(arr) {
    while (arr.length!=1) {
        //取出前两个数组
        let t1 = arr.shift()
        let t2 = arr.shift()
        //进行归并,并把生成的数组压入arr
        arr.push(merge(t1,t2))
    }
    //因为我这种方法最后输出的是一个[[]]形式的长度为1的数组,所以可以进行打平数组
    return arr.flat()
}

这里需要读者理解一下mergeSort函数里的arr.push(merge(t1,t2))

当然我这种写法借助了很多JS的特性,所以看上去会简单一点

到此我们就写完了归并排序了,这种写法是从下往上进行归并,也就是从一个个元素开始,慢慢合并到一个大数组。接下来说一下递归实现。

我们排序都是对一个无序数组进行排序,而归并是对两个有序数组的操作,归并有一定的排序的作用。

我们要创造归并的条件,归并要求两个数组,那我们就一开始把我们的数组分成两个两组传入merge()函数,归并要求两个数组有序,那我们就把这两个数组变成有序,怎么变呢?我们可以用归并排序让它有序,这里就不难看出这是一个递归的过程了。

先对上面的merge函数进行一下封装,套了个壳,主要是为了减少用户传递参数的数量,自动把arr这个数组变成两个数组,传到merge函数里,等待merge函数的返回值。

function merge(arr1, arr2) {
    let result = []
    while (true) { 
        if (arr1.length == 0) {
            result.push(...arr2)
            break
        }
        if (arr2.length == 0) {
            result.push(...arr1)
            break
        }
        if (arr1[0]<arr2[0]) {
            result.push(arr1.shift())
        }else{
            result.push(arr2.shift())
        }
    }
    return result
}

function mergeSort(arr) {
    if (arr.length == 1) {
        return arr
    }
   return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}

那么要在哪里开始递归呢?可以看出前面的merge函数要依赖后面merge函数的结果,那么嵌套在merge函数里的merge函数要放在merge函数内部靠前面的位置

function merge(arr1, arr2) {
    
    arr1 = merge(arr1.slice(0,Math.floor(arr1.length/2)),arr1.slice(Math.floor(arr1.length/2)))
    
    arr2 = merge(arr2.slice(0,Math.floor(arr2.length/2)),arr2.slice(Math.floor(arr2.length/2)))
  
    let result = []
    while (true) { 
        if (arr1.length == 0) {
            result.push(...arr2)
            break
        }
        if (arr2.length == 0) {
            result.push(...arr1)
            break
        }
        if (arr1[0]<arr2[0]) {
            result.push(arr1.shift())
        }else{
            result.push(arr2.shift())
        }
    }
    return result
}

function mergeSort(arr) {
    if (arr.length == 1) {
        return arr
    }
   return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}

但是是不是每次执行merge函数都要递归呢?什么时候不用递归,当传进去的两个参数(两个数组)的长度是1的时候,我们就没有必要对这种数组进行排序了,因为它本来就有序。

然后就有了最后的代码:

function merge(arr1, arr2) {
    if (arr1.length != 1) {
        arr1 = merge(arr1.slice(0,Math.floor(arr1.length/2)),arr1.slice(Math.floor(arr1.length/2)))
    }
    if (arr2.length != 1 ) {
        arr2 = merge(arr2.slice(0,Math.floor(arr2.length/2)),arr2.slice(Math.floor(arr2.length/2)))
    }
    
    let result = []
    while (true) { 
        if (arr1.length == 0) {
            result.push(...arr2)
            break
        }
        if (arr2.length == 0) {
            result.push(...arr1)
            break
        }
        if (arr1[0]<arr2[0]) {
            result.push(arr1.shift())
        }else{
            result.push(arr2.shift())
        }
    }
    return result
}

function mergeSort(arr) {
    if (arr.length == 1) {
        return arr
    }
   return merge(arr.slice(0,Math.floor(arr.length/2)),arr.slice(Math.floor(arr.length/2)))
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值