理解概括
将长的序列通过宽度拆分,
看成逻辑二维序列,对二维序列使用插入排序,
每轮缩小宽度,
逻辑二维序列越来越窄越来越宽直到宽度为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
}
这个算法我的代码还有很大的优化余地,若发现可改进之处,还请品论指教。