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

介绍

在很长一段时间内,排序算法都是停留在时间复杂度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]

。。。

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

代码

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

js 复制代码
// 插入排序算法
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;
}
js 复制代码
 // 希尔排序
 // 插入排序其实就是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)或者更好。希尔排序是一种不稳定的排序算法,通常适用于对大规模数据进行排序。

相关推荐
旧味清欢|6 分钟前
关注分离(Separation of Concerns)在前端开发中的实践演进:从 XMLHttpRequest 到 Fetch API
javascript·http·es6
热爱编程的小曾23 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin35 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
-代号95271 小时前
【JavaScript】十四、轮播图
javascript·css·css3
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187302 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员