likes
comments
collection
share

排序算法(4):希尔排序

作者站长头像
站长
· 阅读数 14

介绍

在很长一段时间内,排序算法都是停留在时间复杂度O(n²)。后来有位科学家名叫希尔,创造出了一种排序方法,突破了O(n²),打破了人们的认知。随后越来越多的优秀算法涌现出来。为了纪念希尔做出的卓越贡献,人们用其名字命名该算法。

希尔排序是对插入排序的优化,甚至可以这么说,插入排序是一种特殊情况下的希尔排序。

算法原理

如果一个数组,它的尾部某个位置上有个很小的数值,根据插入排序算法,它要往前比对很长的队伍才能插入到它的位置。那么希尔就想到,能不能在插入排序之前先尽可能的在一定程度上先做好部分的排序。

他首先引入了一个概念,英文叫做gap。中文翻译有的叫增量,有的叫间距。我觉得间距更贴切些。

就是在数组的首位,隔着一定数量的间距来让这些数值先做一个排序。然后再用一个小间距重复操作。直至gap=1,也就是最后使用插入排序。

这个gap有多种算法,而在希尔原稿中是N/2^k。也就是初始的间距是待排序序列长度的一半,然后每次迭代都将间距减半,直到间距为 1。这个间距序列可以表示为:N/2, N/4, N/8, …, 1。

举例

给一个乱序的数组:[8,5,6,3,9,7,4,2,1]。 数组里有9个数字。我看很多希尔排序教学都喜欢用数组长度为12或8这样的偶数。那么数组奇数的gap怎么设置,数组又该怎么划分很少有去说。

首先gap=9/2=4.5。gap为4.5怎么划分呢?我们可以向下取整,那么gap=4。当然你愿意也可以向上取整。这里就向下取整gap=4。

当gap=4时,数组:[8,5,6,3,9,7,4,2,1]

从第一个数8,往后数4个数字是5,6,3,9。从9开始(这里解释下,与插入排序一样,是从第二个数开始的),那么8,9一组。发现8<9,那么顺序不变。数组为[8,5,6,3,9,7,4,2,1]

9后面是7那么7往前数4个是5,(5,7),5<7,顺序也不变。数组为[8,5,6,3,9,7,4,2,1]

7后面是4,4往前数4个是6,(6,4),6>4,顺序变换。数组为[8,5,4,3,9,7,6,2,1]

因为刚刚4与6换过位置,6后面是2,2往前数4个是3,3<4,顺序不变。数组为[8,5,4,3,9,7,6,2,1]

2后面是1,1往前数4个是9,9>1,顺序变换。数组为[8,5,4,3,1,7,6,2,9]

1后面没有数字了,至此当gap为4时的数组是[8,5,4,3,1,7,6,2,9]

其实就是像一些教学视频一样,先将gap=4找到(8,9,1),(5,7),(6,4),(3,2),(9,1)。再将它们在组内排好从小到大排好,然后再排回到之前对应的位置上。我这边的过程是按照代码的执行过程。

当gap=4/2=2时,数组:[8,5,4,3,1,7,6,2,9] 8往后数2个,是4.那么从4开始。8>4。交换位置[4,5,8,3,1,7,6,2,9](其实这一步我说简单了,先记录4这个值。8>4,就把8这个值赋值给4,数组变成[8,5,8,3,1,7,6,2,9],再往前数两个,发现没有值了。那么就把8这个位置的上的值赋值之前记录的4,于是数组变成[4,5,8,3,1,7,6,2,9])。

由于4和8交换了位置,所以8后面是3,3往前数2个是5,5>3.交换位置 [4,3,8,5,1,7,6,2,9]

。。。

大家领会精神就好,我就不一一例举了。实在有点累。

代码

只要你会插入排序算法,然后做一点小改动,就是希尔排序了

// 插入排序算法
function insetSort(array) {
  const { length } = array;

  for (let i = 1; i < length; i++) {
    let current = array[i];
    let j = i;

    while (j > 0 && array[j - 1] > current) {
      array[j] = array[j - 1];
      j--;
    }
    array[j] = current;
  }

  return array;
}
 // 希尔排序
 // 插入排序其实就是gap=1的时候。
 
 function shellSort(array) {
  const { length } = array;
  let gap = Math.floor(length / 2);
  // 就多了一个外层循环
  while (gap >= 1) {
  // 观察一下,这里就是插入排序算法,只是1都替换成gap
    for (let i = gap; i < array.length; i++) {
      let current = array[i];
      let j = i;
// gap - 1这里解释一下。插入排序是0,当gap为1,1-1=0。
// 它的含义 因为j是不断减小的,当j的位置在gap-1上往左找gap位置必然超过数组的。
      while (j > gap - 1 && array[j - gap] > current) {
        array[j] = array[j - gap];
        j -= gap;
      }
      array[j] = current;
    }
    // 不要忘记gap也要缩小一半
    gap = Math.floor(gap / 2);
  }

  return array;
}
console.log(insetSort([8, 5, 6, 3, 9, 7, 4, 2, 1])); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

时间复杂度

希尔排序的时间复杂度是依赖于gap的选择。在最坏情况下,希尔排序的时间复杂度为O(n²)。但在平均情况下,希尔排序的时间复杂度为O(n logn)或者更好。希尔排序是一种不稳定的排序算法,通常适用于对大规模数据进行排序。