排序算法(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)或者更好。希尔排序是一种不稳定的排序算法,通常适用于对大规模数据进行排序。

相关推荐
前端大卫4 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘19 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare20 分钟前
浅浅看一下设计模式
前端
Lee川24 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端