当前位置: 首页 > news >正文

希尔排序cc

希尔排序

希尔排序是插入排序的改进版本,通过比较相距一定间隔的元素来工作,逐步减小间隔直到只比较相邻元素。

时间复杂度:最佳 O(n log n) | 平均 O(n log² n) | 最差 O(n²)

空间复杂度:O(1)

稳定性:不稳定

应用场景/前提条件

  • 插入排序的改进版
  • 适用于中等规模数据

        

算法讲解

介绍

        希尔排序(Shell Sort)是插入排序的一种改进版本,它是第一个突破O(n²)的排序算法,核心思想是利用步长序列对数据进行分组,在每个分组内使用插入排序,逐步减小步长直到为1,完成最终排序。

        希尔排序时元素会大跨度移动,解决了 插入排序 在处理大规模乱序数组  效率低下的问题,让元素更快移动到正确位置。

算法步骤

  1. 选择一个步长序列,建议初始步长 n/2,每次减半直到步长为1
  2. 对每个步长,对数组进行分组,对应位置相隔为步长的元素视为一组
  3. 对每一组使用  插入排序  进行排序
  4. 减小步长,重复步骤2和3,直到步长减少到1
  5. 当步长为1时,相当于对整个数组做一次插入排序,此时数组已基本有序,所需的比较和移动次数大大减少

为了帮助大家更好的理解,我们以初始数组 [8, 9, 1, 7, 2, 6, 3, 5, 4] 为例,查看每一步的详细流程:

1)第一轮排序

  • 选择间隔 (Gap): 4 (数组长度9除以2取整)
  • 分组: 按照间隔4将数组分为4个子序列。
    • 第1组 (下标0, 4, 8): [8, 2, 4]
    • 第2组 (下标1, 5): [9, 6]
    • 第3组 (下标2, 6): [1, 3]
    • 第4组 (下标3, 7): [7, 5]
  • 对每组进行插入排序:
    • [8, 2, 4] 排序后变为 [2, 4, 8]
    • [9, 6] 排序后变为 [6, 9]
    • [1, 3] 排序后变为 [1, 3]
    • [7, 5] 排序后变为 [5, 7]
  • 本轮结果: 将排序后的元素放回原位置,数组变为 [2, 6, 1, 5, 4, 9, 3, 7, 8]

2)第二轮排序

  • 缩小间隔 (Gap): 2 (上一间隔4除以2)
  • 分组: 此时基于新数组 [2, 6, 1, 5, 4, 9, 3, 7, 8],按照间隔2分为2个子序列。
    • 第1组 (偶数下标): [2, 1, 4, 3, 8]
    • 第2组 (奇数下标): [6, 5, 9, 7]
  • 对每组进行插入排序:
    • [2, 1, 4, 3, 8] 排序后变为 [1, 2, 3, 4, 8]
    • [6, 5, 9, 7] 排序后变为 [5, 6, 7, 9]
  • 本轮结果: 将元素放回原位置,数组变为 [1, 5, 2, 6, 3, 7, 4, 9, 8]。此时数组已经比之前更加有序。

3)第三轮排序(最终轮)

  • 缩小间隔 (Gap): 1。
  • 操作: 当间隔为1时,希尔排序就等同于对整个数组进行一次普通插入排序。
  • 排序过程: 对 [1, 5, 2, 6, 3, 7, 4, 9, 8] 进行插入排序。由于数组已基本有序,这次排序的效率非常高。
  • 最终排序结果: 数组变为完全有序的 [1, 2, 3, 4, 5, 6, 7, 8, 9]

核心特性

  • 递减步长序列:初始较大步长让元素大幅度移动,后续减小步长微调元素位置
  • 分组插入排序:对每个步长形成的分组独立应用插入排序
  • 时间复杂度:取决于步长序列,一般在O(n1.3)到O(n2)之间
  • 不稳定性:相等元素的相对位置在排序后可能会改变
  • 适应性:对于中等大小的数组表现良好

基础实现

public class ShellSort {public static void shellSort(int[] arr) {int n = arr.length;// 初始步长为n/2,每次减半for (int gap = n/2; gap > 0; gap /= 2) {// 对每个步长进行插入排序for (int i = gap; i < n; i++) {// 保存当前元素int temp = arr[i];int j;// 对同一组的元素进行插入排序for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {arr[j] = arr[j - gap];}// 将temp放到正确位置arr[j] = temp;}}}

代码

 

 public static void shellSort(int[] array2) {int gap = array2.length;while (gap > 1) {gap /= 2;shell(array2, gap);//调用直接插入排序方法}}//实现直接插入排序方法public static void shell(int[] array2, int gap) {//i下标从第一组的第二个数据开始for (int i = gap; i < array2.length ; i++) {int tmp = array2[i];//tmp存放i下标的值int j = i - gap;//j下标为i-gap个位置//j每次-gap个位置for (; j >= 0; j-=gap) {if (array2[j] > tmp) {//j下标的值大,将j下标的值放到j变量加上一个gap的位置上array2[j + gap] = array2[j];}else {//j下标的值较小,j下标的值要直接放到j变量加上一个gap的位置上break;}}//此时j下标的值是负数了,将tmp的值放到j变量加上一个gap的位置上array2[j + gap] = tmp;}}

        希尔排序的核心是通过步长(gap)将数组分成多个子序列,对每个子序列进行插入排序。步长初始较大,逐渐减小,最终当步长为1时,整个数组已经基本有序,最后一轮插入排序效率很高。

优化策略

使用优化的步长序列

Knuth提出的步长序列可以提高希尔排序的性能:

public static void knuthShellSort(int[] arr) {int n = arr.length;// 计算初始步长:Knuth序列 h = 3*h + 1int h = 1;while (h < n/3) {h = 3*h + 1;}// 使用Knuth序列进行希尔排序while (h >= 1) {// 对每个步长进行插入排序for (int i = h; i < n; i++) {int temp = arr[i];int j;for (j = i; j >= h && arr[j - h] > temp; j -= h) {arr[j] = arr[j - h];}arr[j] = temp;}// 更新步长h = h/3;}
}

Hibbard序列

Hibbard提出的步长序列也是一种常用优化:

public static void hibbardShellSort(int[] arr) {int n = arr.length;// 计算初始步长:Hibbard序列 2^k - 1int k = 1;while ((1 << k) - 1 < n) {k++;}k--;// 使用Hibbard序列进行希尔排序while (k >= 0) {int gap = (1 << k) - 1;// 对每个步长进行插入排序// ... 与基本实现相同 ...k--;}
}

优缺点

优点

  • 比插入排序更高效,尤其是对于大规模乱序数组
  • 代码简单,容易实现
  • 在中等大小的数组中性能良好
  • 对于几乎已排序的数据效率很高
  • 不需要额外的空间(原地排序)

缺点

  • 不是稳定的排序算法
  • 步长序列的选择对性能影响很大
  • 时间复杂度分析复杂,依赖于所选的步长序列
  • 对于非常大的数据集,其他高级排序算法(如快速排序、堆排序)可能更高效
  • 对于非常小的数据集,简单的插入排序可能更高效

应用场景

  • 中等规模的数据排序
  • 作为更复杂排序算法的预处理步骤
  • 数组基本有序但有少数元素错位较远的情况
  • 嵌入式系统或资源受限环境中的排序
  • 实时系统中需要可预测性能的场景

扩展

结合其他排序算法

        可以将希尔排序与其他排序算法结合使用,例如在最后一轮(gap=1)时使用插入排序的优化版本:

public static void hybridShellSort(int[] arr) {int n = arr.length;// 使用希尔排序进行预处理for (int gap = n/2; gap > 1; gap /= 2) {// ... 标准希尔排序代码 ...}// 最后一轮使用优化的插入排序binaryInsertionSort(arr);
}public static void binaryInsertionSort(int[] arr) {for (int i = 1; i < arr.length; i++) {int key = arr[i];int left = 0;int right = i - 1;// 使用二分查找找到插入位置while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] > key) {right = mid - 1;} else {left = mid + 1;}}// 将元素后移for (int j = i - 1; j >= left; j--) {arr[j + 1] = arr[j];}// 插入元素arr[left] = key;}
}

测验

  1. 希尔排序的时间复杂度取决于什么?
  2. 希尔排序相比于插入排序的主要优势是什么?
  3. 为什么说希尔排序是第一个突破O(n²)的排序算法?

测验答案

  1. 步长序列的选择,不同的步长序列会导致不同的时间复杂度。
  2. 能够让元素进行大跨度移动,更快地接近最终位置,特别适合大规模乱序数组。
  3. 希尔排序在某些步长序列下可以达到O(n^1.3)等 亚二次时间复杂度。
http://www.dtcms.com/a/293494.html

相关文章:

  • 无人机减震模块技术解析
  • Java冒泡排序的不同实现
  • 无人机吊舱减震球模块运行分析
  • 如何在Pico等Android头显中实现无人机低延迟RTMP全景巡检画面播放
  • Cursor(vscode)一些设置
  • 【基于OpenCV的图像处理】图像预处理之图像色彩空间转换以及图像灰度化处理
  • 高亮匹配关键词样式highLightMatchString、replaceHTMLChar
  • 图论的题目整合(Dijkstra)
  • 货车手机远程启动功能的详细使用步骤及注意事项
  • Elasticsearch 字段值过长导致索引报错问题排查与解决经验总结
  • git初始流程
  • [2025CVPR-小目标检测方向]基于特征信息驱动位置高斯分布估计微小目标检测模型
  • 什么是GCN?GCN与GNN有哪些区别?
  • SpringBoot与Vue实战:高效开发秘籍
  • 快手视觉算法面试30问全景精解
  • NumPy核心操作全攻略
  • YOLO12论文阅读:Attention-Centric Real-Time Object Detectors
  • SQLAlchemy 2.0简单使用
  • nodejs模块化
  • 闲庭信步使用图像验证平台加速FPGA的开发:第三十课——车牌识别的FPGA实现(2)实现车牌定位
  • XCKU035‑1SFVA784C Xilinx FPGA KintexUltraScale AMD
  • C# 类 封装 属性 练习题
  • 深度学习 --- 激活函数
  • 计算机底层入门 05 汇编学习环境通用寄存器内存
  • MDC(Mapped Diagnostic Context) 的核心介绍与使用教程
  • LINUX 722 逻辑卷快照
  • (Arxiv-2025)HiDream-I1:一种高效图像生成基础模型,采用稀疏扩散Transformer
  • 在PyCharm中复现LaneNet车道线检测模型
  • JavaScript 01 JavaScript 是什么
  • 医疗系统伪代码