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

【一文了解】八大排序-插入排序、希尔排序

目录

1.插入排序

1.1.核心逻辑

1.2.适用场景

1.3.复杂度

1.4.稳定性

1.5.举例(升序为例)

1)核心动作拆解

2)实例分析([5,2,4,6,1])

1.6.代码实现

2.希尔排序

2.1.核心逻辑

2.2.适用场景

2.3.复杂度

2.4.稳定性

2.5.举例(升序为例)

1)核心动作拆解

2)实例分析([12,34,54,2,3])

2.6.代码实现

3.测试

3.1.完整代码

3.2.测试结果

4.总结


       本篇文章来分享一下八大排序中的插入排序与希尔排序。

1.插入排序

1.1.核心逻辑

       将元素逐个插入"已排序部分"的正确位置(类似整理手牌:拿一张,插入前面合适位置)

1.2.适用场景

       小规模数据、部分有序数据

1.3.复杂度

1)时间复杂度:O(n²)(最坏/平均),O(n)(最优)

●最坏情况:O(n²)(完全逆序,每次插入需移动已排序部分所有元素)。

●平均情况:O(n²)(随机数据,插入位置平均在已排序部分中间,移动次数平均为n²/4)。

●最优情况:O(n)(完全有序,无需移动元素,仅需n-1次比较)。

2)空间复杂度:O(1)(原地排序)

1.4.稳定性

       稳定(相同元素插入到已存在元素后方,不改变相对位置)

1.5.举例(升序为例)

1)核心动作拆解

分区思想:始终将数组分为“已排序部分”(左)和“未排序部分”(右),初始已排序部分只有第一个元素。

插入逻辑

从“未排序部分”取第一个元素作为“待插入元素”。

从“已排序部分”的末尾开始,向前逐个比较:

若“已排序元素”大于“待插入元素”,则将“已排序元素”后移一位(腾出位置)。

若“已排序元素”小于或等于“待插入元素”,则停止比较,将“待插入元素”插入到当前位置的后一位。

终止条件:“未排序部分”的所有元素都被插入到“已排序部分”,此时整个数组有序。

2)实例分析([5,2,4,6,1])

●初始状态
    已排序部分:[5](默认数组第一个元素有序)。
    未排序部分:[2, 4, 6, 1](需要逐个处理的元素)。
●插入第 1 个未排序元素 2
    比较 2 和已排序部分的最后一个元素 5:2 < 5(逆序),需将 5 后移一位(腾出位置 → 已排序部分变为 [5, 5]
    将 2 插入到空位(已排序部分的开头),结果:[2, 5]。
    本轮结果:
        已排序部分:[2, 5]。
        未排序部分:[4, 6, 1]。
●插入第 2 个未排序元素 4
    比较 4 和已排序部分的最后一个元素 5:4 < 5(逆序),将 5 后移一位 → 已排序部分临时变为 [2, 5, 5]。
    比较 4 和前一个元素 2:4 > 2(正序),无需继续后移,找到插入位置。
    将 4 插入到 2 和 5 之间,结果:[2, 4, 5]。
    本轮结果:
        已排序部分:[2, 4, 5]。
        未排序部分:[6, 1]。
●插入第 3 个未排序元素 6
    比较 6 和已排序部分的最后一个元素 5:6 > 5(正序),无需移动任何元素。
    直接将 6 插入到已排序部分的末尾,结果:[2, 4, 5, 6]。
    本轮结果:
        已排序部分:[2, 4, 5, 6]。
        未排序部分:[1]。
●插入最后一个未排序元素 1
    比较 1 和 6:1 < 6 → 6 后移 → 临时 [2, 4, 5, 6, 6]。
    比较 1 和 5:1 < 5 → 5 后移 → 临时 [2, 4, 5, 5, 6]。
    比较 1 和 4:1 < 4 → 4 后移 → 临时 [2, 4, 4, 5, 6]。
    比较 1 和 2:1 < 2 → 2 后移 → 临时 [2, 2, 4, 5, 6]。
    已到已排序部分的开头,将 1 插入到最前面,结果:[1, 2, 4, 5, 6]。
●最终结果:[1, 2, 4, 5, 6]。

1.6.代码实现

/// <summary>
/// 插入排序
/// </summary>
/// <param name="arr">待排序数组(会直接修改原数组)</param>
/// <param name="isAscending">排序方向:true=升序,false=降序</param>
public static void InsertionSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
{if (arr == null || arr.Length <= 1) return;int n = arr.Length;//外层循环:从第2个元素开始(第1个元素默认已序)for (int i = 1; i < n; i++){T current = arr[i];//待插入的"当前元素"(暂存,避免后续移位覆盖)int j = i - 1;     //已排序部分的末尾索引//内层循环:将"已排序部分"中比current大/小的元素后移,腾出插入位置while (j >= 0){int compareResult = arr[j].CompareTo(current);bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);if (needSwap){arr[j + 1] = arr[j];//元素后移j--;}else{break;//找到插入位置,退出循环}}//将current插入到正确位置(j+1:因循环结束时j已多减1)arr[j + 1] = current;}
}

2.希尔排序

2.1.核心逻辑

       按"增量"将数组分组,对每组执行插入排序;逐步减小增量至1(此时数组接近有序,插入排序效率高)

2.2.适用场景

       中等规模数据(n<10^5)、对稳定性无要求的场景

2.3.复杂度

1)时间复杂度:O(n^1.3)(平均,取决于增量选择)、O(n²)(最坏,增量为1时退化为插入排序,但实际表现仍优于直接插入排序),无最优情况O(n),因为需要多轮分组排序,即使数组有序,仍需处理增量过程。

2)空间复杂度:O(1)(原地排序)

2.4.稳定性

       不稳定(分组排序时,相同元素可能被分到不同组,交换后破坏相对位置)

2.5.举例(升序为例)

1)核心动作拆解

       核心思路:“分组优化插入排序”,插入排序的效率在数组“基本有序”时极高(接近O(n)),但对完全无序的数组效率很低(O(n²))。希尔排序的优化逻辑是:

●先让数组“基本有序”:通过“较大间隔”将数组分成多个子数组,对每个子数组执行插入排序(此时子数组长度短,插入排序效率高)。

●逐步缩小间隔:每轮排序后减小间隔,重复分组排序,使数组越来越接近有序。

●最后用间隔1排序:当间隔缩小到1时,数组已基本有序,此时执行一次插入排序即可完成最终排序(此时插入排序效率接近O(n))。

       间隔(又称“增量”)是希尔排序的核心参数,决定了如何分组:

●初始间隔通常取数组长度的一半(gap=n/2),后续每轮减半(gap=gap/2),直到gap=1(这是最经典的间隔选择方式,也可使用其他间隔序列如斐波那契数列)。

●分组规则:间隔为gap时,数组被分为gap个子数组,每个子数组包含“下标相差gap的元素”。例如,gap=2时,数组[a0,a1,a2,a3,a4,a5]被分为两个子数组:[a0,a2,a4]和[a1,a3,a5]。

2)实例分析([12,34,54,2,3])

       使用经典间隔:gap=2→gap=1。

●初始数组
    [12, 34, 54, 2, 3](长度 n=5)
●第 1 轮:间隔 gap=2(n/2=2,向下取整)
    ○分组:按 gap=2 分为 2 个子数组:
        子数组 1:[12, 54, 3](下标 0, 2, 4)
        子数组 2:[34, 2](下标 1, 3)
   ○ 对每个子数组执行插入排序:
        子数组 1 排序:[12, 54, 3]
        初始已排序部分:[12],未排序元素:54、3。
            插入 54:54 > 12(正序),直接放末尾 → 已排序部分:[12, 54]。
           插入 3:3 < 54 → 54 后移 → 临时 [12, 54, 54]; 3 < 12 → 12 后移 → 临时[12, 12, 54] 插入3到开头 → 已排序部分:[3, 12, 54]。
        子数组 2 排序:[34, 2]
            初始已排序部分:[34],未排序元素:2。
            插入 2:2 < 34 → 34 后移 → 临时 [34, 34]  插入 2 到开头 → 已排序部分:[2, 34]。
    本轮结果:[3, 2, 12, 34, 54](数组已更接近有序)
●第 2 轮:间隔 gap=1(gap/2=1)
    ○分组:gap=1 时,整个数组为一个子数组 [3, 2, 12, 34, 54]。
    ○对每个子数组执行插入排序:
         初始已排序部分:[3],未排序元素:2、12、34、54。
        插入 2:2 < 3 → 3 后移 → 临时 [3, 3, 12, 34, 54],插入 2 到开头 → 已排序部分:[2, 3]。
        插入 12:12 > 3(正序),直接放末尾 → 已排序部分:[2, 3, 12]。
        插入 34:34 > 12(正序),直接放末尾 → 已排序部分:[2, 3, 12, 34]。
        插入 54:54 > 34(正序),直接放末尾 → 已排序部分:[2, 3, 12, 34, 54]。
●最终结果:[2, 3, 12, 34, 54]

2.6.代码实现

/// <summary>
/// 希尔排序
/// </summary>
/// <param name="arr">待排序数组(会直接修改原数组)</param>
/// <param name="isAscending">排序方向:true=升序,false=降序</param>
public static void ShellSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
{if (arr == null || arr.Length <= 1) return;int n = arr.Length;//增量初始值:数组长度的一半,后续每次减半(经典增量选择,简单高效)for (int gap = n / 2; gap > 0; gap /= 2){//对每个"增量组"执行插入排序(i从gap开始,遍历所有组的元素)for (int i = gap; i < n; i++){T current = arr[i];//待插入的当前元素int j = i;//组内元素比较:按增量gap向前遍历,比current大/小则移位while (j >= gap){int compareResult = arr[j - gap].CompareTo(current);bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);if (needSwap){arr[j] = arr[j - gap];//组内元素按增量移位j -= gap;}else{break;}}//插入当前元素到组内正确位置arr[j] = current;}}
}

3.测试

3.1.完整代码

using System;
using System.Collections.Generic;
using UnityEngine;public class SortTest : MonoBehaviour
{private void Start(){int[] arr3 = GenerateArray(10);PrintArray(arr3);InsertionSort(arr3);PrintArray(arr3, "插入排序后,数组内容:");int[] arr4 = GenerateArray(10);PrintArray(arr4);ShellSort(arr4);PrintArray(arr4, "希尔排序后,数组内容:");}private int[] GenerateArray(int count, int minValue = 0, int maxValue = 100){List<int> arr = new List<int>();for (int i = 0; i < count; i++){int value = UnityEngine.Random.Range(minValue, maxValue);arr.Add(value);}return arr.ToArray();}/// <summary>/// 插入排序/// </summary>/// <param name="arr">待排序数组(会直接修改原数组)</param>/// <param name="isAscending">排序方向:true=升序,false=降序</param>public static void InsertionSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>{if (arr == null || arr.Length <= 1) return;int n = arr.Length;//外层循环:从第2个元素开始(第1个元素默认已序)for (int i = 1; i < n; i++){T current = arr[i];//待插入的"当前元素"(暂存,避免后续移位覆盖)int j = i - 1;     //已排序部分的末尾索引//内层循环:将"已排序部分"中比current大/小的元素后移,腾出插入位置while (j >= 0){int compareResult = arr[j].CompareTo(current);bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);if (needSwap){arr[j + 1] = arr[j];//元素后移j--;}else{break;//找到插入位置,退出循环}}//将current插入到正确位置(j+1:因循环结束时j已多减1)arr[j + 1] = current;}}/// <summary>/// 希尔排序/// </summary>/// <param name="arr">待排序数组(会直接修改原数组)</param>/// <param name="isAscending">排序方向:true=升序,false=降序</param>public static void ShellSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>{if (arr == null || arr.Length <= 1) return;int n = arr.Length;//增量初始值:数组长度的一半,后续每次减半(经典增量选择,简单高效)for (int gap = n / 2; gap > 0; gap /= 2){//对每个"增量组"执行插入排序(i从gap开始,遍历所有组的元素)for (int i = gap; i < n; i++){T current = arr[i];//待插入的当前元素int j = i;//组内元素比较:按增量gap向前遍历,比current大/小则移位while (j >= gap){int compareResult = arr[j - gap].CompareTo(current);bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);if (needSwap){arr[j] = arr[j - gap];//组内元素按增量移位j -= gap;}else{break;}}//插入当前元素到组内正确位置arr[j] = current;}}}/// <summary>/// 打印数组内容/// </summary>/// <param name="arr"></param>public static void PrintArray<T>(T[] arr, string prefix = "数组内容:") where T : IComparable<T>{if (arr == null){Debug.Log($"{prefix} null");return;}Debug.Log($"{prefix} [{string.Join(", ", arr)}]");}
}

3.2.测试结果

4.总结

       插入排序与希尔排序同属“插入类排序”,核心逻辑均基于“将元素插入已排序部分”,但希尔排序通过“分组插入”优化了插入排序的效率。小数据/有序数据用插入排序(简单、稳定、高效);中大数据/无序数据用希尔排序(效率高、空间省)。二者共同覆盖了“小规模到中大规模”的排序需求,且都无需额外空间,适合内存有限的场景

对比维度

插入排序(InsertionSort)

希尔排序(ShellSort)

核心逻辑

将元素逐个插入"已排序部分"的正确位置

按"增量"将数组分组,对每组执行插入排序;逐步减小增量至1(此时数组接近有序,插入排序效率高)

时间复杂度

O(n²)(最坏/平均),O(n)(最优)

O(n^1.3)(平均)、O(n²)(最坏)

空间复杂度

O(1)(原地排序,仅需1个临时变量存储当前待插入元素)

O(1)(原地排序,仅需临时变量存储待插入元素和增量)

稳定性

稳定(插入时相同值元素不会被交换,保留原相对顺序)

不稳定(分组插入时,相同值元素可能被分到不同子数组,插入后相对顺序被打乱)

适用场景

1.小规模数据(n<1000);

2.接近完全有序的数据:可触发O(n)最优复杂度,效率远超选择/冒泡排序;

3.需稳定排序的场景(如排序含相同值的结构化数据);

4.数据流式处理(边接收数据边插入排序,实时维护有序序列)。

1.中大规模数据(n=1000~100000):效率远高于插入排序(O(n²)→O(n^(3/2))),且实现比快速排序简单;

2.对稳定性无要求的场景(如纯数值排序,无需保留相同值的原顺序);

3.硬件资源有限的场景(空间复杂度O(1),无额外内存消耗)。

       想要了解冒泡排序和选择排序,可以参考【一文了解】八大排序-冒泡排序、选择排序

       好了,本次的分享到这里就结束啦,希望对你有所帮助~

http://www.dtcms.com/a/524029.html

相关文章:

  • n8n数据存储在postgres
  • 数据结构——冒泡排序
  • 医疗连续体机器人模块化控制界面设计(2025年更新版Python库)
  • 做网站服务器需要系统wordpress折腾怕了
  • 022数据结构之树状数组——算法备赛
  • 从 TypeScript 到 Java(4):访问修饰符与作用域 —— Java 的封装哲学
  • 做网站要有什么团队上海网站营销推广
  • 残差网络的介绍及ResNet-18的搭建(pytorch版)
  • WPF绘制界面常用功能
  • vbs笔记 【未完更】
  • 不用服务器也能搭博客!Docsify+cpolar的极简方案
  • 一文了解开源大语言模型文件结构,以 Hugging Face DeepSeek-V3.1 模型仓库为例
  • 艾体宝洞察 | CRA 合规冲刺指南:艾体宝 ONEKEY 独家报告首发,洞察全球企业合规进度!
  • 网站设计方法常州网站制作维护
  • iOS 26 App 开发阶段性能优化 从多工具协作到数据驱动的实战体系
  • Nginx 配置解析与性能优化
  • vLLM 性能优化实战:批处理、量化与缓存配置方案
  • 【前端】前端浏览器性能优化的小方法
  • google广告联盟网站服务平台型网站
  • Android GPU的RenderThread Texture upload上传Bitmap优化prepareToDraw
  • 10.1 网络规划与设计——结构化布线系统
  • 国产麒麟、uos在线编辑数据库中的文件
  • 从零开始的C++学习生活 15:哈希表的使用和封装unordered_map/set
  • 【图像处理基石】通过立体视觉重建建筑高度:原理、实操与代码实现
  • 金融培训网站源码国内可以做的国外兼职网站
  • 东莞网站设计制作网站个人网页设计需求分析
  • 率先发布!浙人医基于KingbaseES构建多院区异构多活容灾新架构
  • CSS 样式用法大全
  • Chrome旧版本下载
  • 浙江省建设网站首页html网站源代码