插入排序
写希尔排序之前,我们再来回顾一下插入排序
/**
* @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;
}