一、实现希尔排序
希尔排序可以说是排序算法中的一个里程碑了,打破人们对排序算法的时间复杂度只能是O(n**2)的思维禁锢;
上一节我们知道了效率比冒泡和选择高的插入排序,那有没有思考过当标记位是一个很小的元素,那岂不是要从有序队列的最后一位一直比较到最前面吗,这样效率不是很高;可不可以,减少比较次数呢?比如大的往后面靠靠,小的往前面靠靠,可以的,希尔排序就是依次为核心:
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了。
这样说很抽象还是,打个比方吧,有20个数字进行排序,第一次间隔我们取20/2,相当于把20个数字分为10组,每组分别进行不影响其他组的插入排序;第二次间隔我们取10/2,20个数字分为5组,分别进行不影响其他组的插入排序;第三次间隔取5/2的向下取整2,分为10组,再次对各个组插入排序;最后一次间隔取2/2,也就是分为20组,对其插入排序,这就是普通的插入排序了,而且效率非常高,因为每个元素离自己该去的位置都很近;
从上面的例子就可以看出,步长我们是先取序列元素个数的一半,后面依次减半,不是整数的向下取整,直到步长为1;
我们先把框架写出来:
function shellSort(array) {
let length = array.length;
// 取间隔
let gap = Math.floor(length / 2);
while (gap >= 1) {
gap = Math.floor(gap / 2);
}
return array;
}
while里面就是对应步长的插入排序了,这么一看是不是感觉希尔排序还是不难;
二、具体实现
function shellSort(array) {
let length = array.length;
// 取间隔
let gap = Math.floor(length / 2);
while (gap >= 1) {
// 插入排序,从第二个位置开始比较
for (let i = gap; i < length; i++) {
// 获取并标记插入元素位置
let index = i;
let temp = array[index];
while (temp < array[index - gap] && index > gap - 1) {
array[index] = array[index - gap];
index -= gap;
}
array[index] = temp;
}
gap = Math.floor(gap / 2);
}
return array;
}
为什么 let i = gap ? 因为插入排序是从第二个元素开始的,而0+gap = gap 不就是第二个元素的位置吗
为什么 temp < array[index - gap] && index > gap - 1? 和普通插入排序一样,我们不知道要循环几次,所以用while,比标记元素大则向后移动gap 位,而index>gap -1,是防止index为负数;