[路飞]_leetcode刷题_希尔排序

191 阅读2分钟

插入排序

写希尔排序之前,我们再来回顾一下插入排序

/**
 * @param {number[]} nums
 * @return {number[]}
 */
 var sortArray = function(nums) {
    // cur等待插入的元素
    // pt是cur当前位置的指针,会随着cur移动
    let cur,pt;
    for(let i=0;i<nums.length;i++){
        cur = nums[i];
        pt = i;
        // 如果pt-1>=0,代表cur前面还有数,如果前面都没有数了,肯定也不用再循环了,所以pt-1>=0
        // 如果cur>nums[pt-1],说明当前项比前一项大,那么也不用移动了,跳出当次插入
        while(pt-1>=0&&cur<nums[pt-1]){
            // 如果cur 小于 前一项,那么将cur对应的pt位置的元素赋值为pt-1的值,
            // 不用担心pt位置元素会弄丢,pt位置元素一直保存在cur变量里;
            // 这里也可以理解为pt位置就是一个空的,值一开始就被拿走了。
            nums[pt] = nums[pt-1];
            pt--
        }
        // 然后再将cur放回到pt位置,就完成了一次插入操作。
        // 这里可以做一个判断,如果pt!=i,那么再赋值,否则说明cur根本都没有移动
        if(pt != i){
            nums[pt] = cur;
        }
    }
    return nums;
};

希尔排序

希尔排序比较讲究的就是增量(间隔),一般我们都是采用依次减半的方式去做增量(间隔)处理。

我里特意将希尔排序内部插入实现写的跟上面插入排序比较像,好做比较

/**
 * @param {number[]} nums
 * @return {number[]}
 */
function sortArray(nums) {
    let len = nums.length;
    // 定义间隔,这里间隔我们会依次减半,最后为1
    let gap = Math.floor(len/2);
    // 最后我们要处理的间隔为1,所以跳出循环条件为间隔大于0
    while(gap>0){
        // 从i=gap开始做插入排序,这样能保证我们可以和gap-gap即0号位元素比较,开始去做插入排序
        // 这里随着i的增加,被我们分组的不同组,其实是在轮流做单次的插入排序
        for(let i=gap;i<len;i++){

            let cur = nums[i];
            let pt = i;
            // 以gap为间隔,从当前元素开始向前找元素比较,做插入排序
            while(pt-gap>=0 && nums[pt-gap]>cur){
                nums[pt] = nums[pt-gap];
                // 这里,我们上面插入排序pt--,是每次与前一位比较看是否需要插入
                // 而这里是每次与前gap为比较,看是否要插入
                pt-=gap;// 正常我们这里是pt--
            }
            if(pt != i){
                nums[pt] = cur;
            }
        }
        // gap会以每次减半的方式递减,最终为1
        gap=Math.floor(gap/2)
    }
    return nums;
}

目前增量序列的实现有很多种,比如,Hibbard增量序列、Knuth增量序列、Sedgewick增量序列,具体哪种效率最高好像仍有待证明

我们这里采用Knuth增量序列( 即gap在数组[1,4,13,40,...,3*h+1]中取数 )

演示如下,其他的大家自行百度

/**
 * @param {number[]} nums
 * @return {number[]}
 */
function sortArray(nums) {
    let len = nums.length;
    let gap=1;
    // Knuth增量序列为gap = 3*gap + 1,即[1,4,13,40....]
    // 那我们按这个公式找到gap的最大值
    // 后面针对每个增量(间隔)循环结束后,对增量(间隔)做除以3向下取整操作,Math.floor(gap/3)
    while (3 * gap + 1 < len) {
        gap = 3 * gap + 1;
    }
    while(gap>0){
        for(let i=gap;i<len;i++){
            let cur = nums[i];
            let pt = i;
            while(pt-gap>=0 && nums[pt-gap]>cur){
                nums[pt] = nums[pt-gap];
                pt-=gap;
            }
            nums[pt] = cur;
        }
        // 这里对gap做如下操作,保证gap是在数组[1,4,13,40...]里向下取数
        gap=Math.floor(gap/3)
    }
    return nums;
}