js实现希尔排序

146 阅读2分钟

理解概括

将长的序列通过宽度拆分,
看成逻辑二维序列,对二维序列使用插入排序,
每轮缩小宽度,
逻辑二维序列越来越窄越来越宽直到宽度为1只有再一次的插入排序后变得有序。

实例描述

将序列看做是二维的,首先取宽度为序列长度1/2向下取整再加一,
然后循环缩小宽度,
宽度越来越窄,高度越来越高,
直到宽度唯一做最后一次插入排序即可。
下面例子中的宽度是方便举例用,实际取宽度不是递减1的
比如:[2,45,6,89,21,3,7],取初始宽度为4
第一轮:
[ 2,45,6,89],
[21, 3,7]
一轮排序后:(每轮上下排序)
[ 2, 3,6,89],
[21,45,7]
结果将其合并:[2,3,6,89,21,45,7]
第二轮:取宽度为3
[ 2, 3, 6][89,21,45],
[ 7]
二轮排序后:
[ 2, 3, 6][ 7,21, 45],
[89]
结果为:[2, 3, 6,7,21, 45,89]
第三轮:取宽度为2
[ 2, 3],
[ 6, 7],
[21,45],
[89]
三轮排序后:
[ 2, 3],
[ 6, 7],
[21,45],
[89]
结果为:[2, 3, 6, 7,21,45,89]
第四轮(当前宽度为1了,所以是最后一轮):宽度1
[ 2],
[ 3],
[ 6],
[ 7],
[21],
[45],
[89],
第四轮排序后:
[ 2],
[ 3],
[ 6],
[ 7],
[21],
[45],
[89],
结果为:[2, 3, 6, 7,21,45,89]
总结:
其实我们在第三轮排序的时候序列就已经是有序的了,
当然这里只是运气好,宽度为1这一轮是必须的。
疑问:
既然最后一轮就相当于对整个序列的插入排序,我们之前的那些操作不是多余的了吗?
解答:
显然不是,每一轮都可以让序列变得更相对有序,
插入排序的最大缺点也就是大量的序列内元素移动操作,
此前的每一次操作的元素移动都不会超过序列长度除以宽度,
到最后一步时,需要移动的元素也大大减少。

代码

function shellSort(arr){
    //划分宽度
    let w = Math.floor(arr.length/2)+1
    //不断循环缩小宽度
    for(w;w>0;w=Math.floor(w/3)){
        //根据宽度划分逻辑二维序列
        for(let i = 0;i<Math.floor(arr.length/w);i++){
            //遍历组逻辑二维序列
            for(let j = w+i;j<arr.length;j=j+w){
                //对当前遍历的这组二维序列使用插入排序
                for(let pre = i;pre<j;pre = pre+w){
                    //如果当前元素小于之前有序序列的某个元素,则插入
                    if(arr[j]<arr[pre]){
                        //暂存需要插入的那个值
                        let temp = arr[j]
                        //将要被插入的位置及后面的元素右移一位
                        for(let rm = j;rm>pre;rm = rm-w){
                            arr[rm] = arr[rm-w]
                        }
                        //前面右移一位后腾出空位,现在插入
                        arr[pre] = temp
                    }
                }
            }
        }
    }
    return arr
}

这个算法我的代码还有很大的优化余地,若发现可改进之处,还请品论指教。