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

相关推荐
Csvn8 小时前
OpenSpec 详细使用教程
前端
之歆8 小时前
Day19_LESS 完全指南——从入门到工程实践
前端·css·less
云水一下9 小时前
HTML5 从入门到精通:实战收官——从零搭建完整静态网站,综合运用所有知识
前端·html5
不总是9 小时前
Windows 系统 Node.js 免安装版(zip)安装与配置教程(2026 最新)
前端·windows·node.js
冬奇Lab10 小时前
每日一个开源项目(第105篇):Twenty - 跳出 Salesforce 的圈套,定义现代开源 CRM
前端·后端·开源
zhangyao94033010 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
XinZong11 小时前
实测OpenClaw虾淘:全民工具AI时代,冷门非工具类的Skill还能出圈吗?
javascript
kjs--11 小时前
浏览器书签执行脚本
前端
烛衔溟11 小时前
TypeScript 类的类型 —— 作为类型使用
javascript·ubuntu·typescript
之歆11 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化