【希尔排序算法】详解:原理、实现与优化

【希尔排序算法】详解:原理、实现与优化

【希尔排序算法】详解:原理、实现与优化

🌺The Begin🌺点点关注,收藏不迷路🌺

一、算法概述

希尔排序(Shell Sort)是Donald Shell于1959年提出的一种改进的插入排序算法,又称缩小增量排序。它通过将原始数组分解为若干个子序列进行插入排序,随着增量逐渐减小,最终对整个数组进行一次插入排序。

基本特性

  • 时间复杂度:取决于增量序列,通常为O(n^(1.3-2))
  • 空间复杂度:O(1)(原地排序)
  • 稳定性:不稳定排序算法
  • 适用场景:中等规模数据,特别是部分有序的数据

二、算法原理详解

核心思想

  1. 分组插入排序:按照增量gap将数组分为若干子序列
  2. 逐步缩小增量:每次缩小gap值,直到gap=1
  3. 最终插入排序:当gap=1时,就是标准的插入排序

增量序列选择

常用的增量序列:

  • Shell原始序列:gap = n/2, n/4, …, 1
  • Hibbard序列:1, 3, 7, …, 2^k-1
  • Sedgewick序列:1, 5, 19, 41, 109,…

三、算法流程图示

示例数组:[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]

初始状态
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 8 │ 9 │ 1 │ 7 │ 2 │ 3 │ 5 │ 4 │ 6 │ 0 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 
第一轮:gap=5

将数组分为5组:(8,3), (9,5), (1,4), (7,6), (2,0)

分组示意图: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 8 │ │ │ │ │ 3 │ │ │ │ │ → (8,3) │ │ 9 │ │ │ │ │ 5 │ │ │ │ → (9,5) │ │ │ 1 │ │ │ │ │ 4 │ │ │ → (1,4) │ │ │ │ 7 │ │ │ │ │ 6 │ │ → (7,6) │ │ │ │ │ 2 │ │ │ │ │ 0 │ → (2,0) └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 排序后结果: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 3 │ 5 │ 1 │ 6 │ 0 │ 8 │ 9 │ 4 │ 7 │ 2 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 
第二轮:gap=2

将数组分为2组:
(3,1,0,9,7), (5,6,8,4,2)

分组示意图: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 3 │ │ 1 │ │ 0 │ │ 9 │ │ 7 │ │ → (3,1,0,9,7) │ │ 5 │ │ 6 │ │ 8 │ │ 4 │ │ 2 │ → (5,6,8,4,2) └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 排序后结果: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 0 │ 2 │ 1 │ 4 │ 3 │ 5 │ 7 │ 6 │ 9 │ 8 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 
第三轮:gap=1(标准插入排序)
最终排序结果: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 

四、完整Java实现

packagecom.kwan.springbootkwan.controller;publicclassShellSort{/** * 基础希尔排序(使用Shell原始增量序列) * @param arr 待排序数组 */publicstaticvoidshellSort(int[] arr){if(arr ==null|| arr.length <=1){return;}int n = arr.length;// 初始gap为数组长度的一半,逐步缩小gap直到1for(int gap = n /2; gap >0; gap /=2){// 从第gap个元素开始,对各个子序列进行插入排序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];} arr[j]= temp;}}}// 测试代码publicstaticvoidmain(String[] args){int[] arr ={8,9,1,7,2,3,5,4,6,0};System.out.println("原始数组:");printArray(arr);System.out.println("\n希尔排序后:");shellSort(arr);printArray(arr);}// 打印数组privatestaticvoidprintArray(int[] arr){for(int num : arr){System.out.print(num +" ");}System.out.println();}}
在这里插入图片描述

五、算法分析

时间复杂度分析

希尔排序的时间复杂度取决于增量序列的选择

增量序列最坏时间复杂度平均时间复杂度
Shell原始序列O(n²)O(n^(1.5))
Hibbard序列O(n^(3/2))O(n^(5/4))
Sedgewick序列O(n^(4/3))O(n^(7/6))

空间复杂度

  • 只需要常数级别的额外空间
  • 空间复杂度:O(1)

稳定性

希尔排序是不稳定的排序算法,因为相同的元素可能会被分到不同的子序列中,导致相对顺序改变。

六、实际应用场景

  1. 中等规模数据排序:当数据量在几千到几万时,希尔排序表现良好
  2. 嵌入式系统:由于空间复杂度低,适合资源受限的环境
  3. 作为更复杂算法的预处理步骤:可以先使用希尔排序进行部分排序

七、与其他排序算法的对比

算法平均时间复杂度最坏时间复杂度空间复杂度稳定性
希尔排序O(n^(1.3-2))O(n²)O(1)不稳定
插入排序O(n²)O(n²)O(1)稳定
快速排序O(nlogn)O(n²)O(logn)不稳定
归并排序O(nlogn)O(nlogn)O(n)稳定

八、总结

希尔排序通过分组插入排序的思想,有效减少了数据移动的次数,是对简单插入排序的重要改进。虽然其时间复杂度理论分析较为复杂,但在实际应用中,特别是中等规模数据的排序场景下,希尔排序往往能提供不错的性能表现。

选择合适的增量序列对希尔排序的性能至关重要,Hibbard序列和Sedgewick序列在实践中表现良好。希尔排序的实现简单,空间效率高,是值得掌握的经典排序算法之一。

在这里插入图片描述

🌺The End🌺点点关注,收藏不迷路🌺

Read more

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

目录 前言 一、什么是单调栈?先打破 “栈” 的常规认知 1.1 单调栈的核心特性 1.2 如何实现一个单调栈? 实现单调递增栈 实现单调递减栈 1.3 核心操作解析:为什么要 “弹出元素”? 二、单调栈能解决什么问题?四大核心场景全覆盖 2.1 场景 1:找左侧最近的 “更大元素” 问题描述 解题思路 代码实现 测试用例验证 2.2 场景 2:找左侧最近的 “更小元素” 问题描述 解题思路 代码实现 测试用例验证 2.3 场景 3:找右侧最近的 “更大元素” 问题描述

By Ne0inhk

傅里叶变换 | FFT 与 DFT 原理及算法

注:本文为 “傅里叶变换 | FFT 与 DFT” 相关合辑。 英文引文,机翻未校。 中文引文,略作重排。 图片清晰度受引文原图所限。 如有内容异常,请看原文。 Fast Fourier Transform (FFT) 快速傅里叶变换(FFT) In this section we present several methods for computing the DFT efficiently. In view of the importance of the DFT in various digital signal processing applications, such as linear filtering,

By Ne0inhk
链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧

链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧

✨链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧🎯 * 视频地址 * 🚀 开篇引论:链表反转的进阶之路 * 🔄 基础筑基:链表【前n个节点】递归反转 * 1. 函数定义与核心功能 * 2. 递归实现思路拆解 * 3. 直观调用示例 * 4. 关键代码实现(C++)与详解 * 🎯 实战攻坚:LeetCode 92 链表区间反转 * 1. 题目问题描述 * 2. 神器加持:虚拟头节点(哨兵)技巧 * 3. 整体解题思路 * 4. 完整代码实现(C++)与逐行解析 * 5. 算法复杂度分析 * 📚 算法原理深度剖析 * 1. 递归反转的核心原理 * 2. 虚拟头节点的底层逻辑 * 💡 算法学习核心建议 * 结语 * ✅ 关键点回顾 视频地址

By Ne0inhk
贪心算法(局部最优实现全局最优)第一篇

贪心算法(局部最优实现全局最优)第一篇

目录 1. 什么是贪心算法 2. 贪心算法的解题步骤 3. 具体例题及代码 3.1 LeetCode860. 柠檬水找零 3.2 LeetCode2208. 将数组和减半的最少操作次数 3.3 LeetCode179. 最大数 从这篇文章开始,我们开始讲解贪心算法。 1. 什么是贪心算法 贪心算法是算法设计中的经典思想,核心逻辑用一句话就能概括 ——每一步都做出当前情况下的最优选择,不回头、不纠结,最终期望得到全局最优解。它不像动态规划那样依赖中间状态存储,也不用回溯尝试所有可能,凭借 “直来直往” 的思路,成为解决特定问题的高效方案。 2. 贪心算法的解题步骤 1. 问题拆解:将复杂问题拆分成多个连续的局部决策步骤。 2. 确定贪心策略:明确每一步的 “最优标准”(比如 “选最小”“选最大”“选最早结束”)。 3. 验证可行性:

By Ne0inhk