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

目录

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.代码实现

cs 复制代码
/// <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.代码实现

cs 复制代码
/// <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.完整代码

cs 复制代码
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),无额外内存消耗)。 |

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

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

相关推荐
Icoolkj3 小时前
Edge-TTS+Cloudflare Worker:免费 TTS 服务搭建指南,支持 API 调用与低代码集成
1024程序员节
小莞尔3 小时前
【51单片机】【protues仿真】基于51单片机智能温控风扇系统
c语言·单片机·嵌入式硬件·物联网·51单片机·1024程序员节
呆呆小金人3 小时前
Linux:开源时代的隐形基石
linux·1024程序员节
没有bug.的程序员3 小时前
Spring 常见问题与调试技巧
java·后端·spring·动态代理·1024程序员节
Han.miracle3 小时前
数据结构——排序的超级详解(Java版)
java·数据结构·学习·算法·leetcode·排序算法·1024程序员节
hazy1k3 小时前
51单片机基础-DS18B20温度传感器
c语言·stm32·单片机·嵌入式硬件·51单片机·1024程序员节
AI棒棒牛3 小时前
论文精读系列:Retinanet——目标检测领域中的SCI对比实验算法介绍!可一键跑通的对比实验,极大节省小伙伴的时间!!!
yolo·目标检测·计算机视觉·对比实验·1024程序员节·创新·rtdter
胜天半月子3 小时前
嵌入式开发 | C语言 | 单精度浮点数解疑--为什么规格化数中指数位E不能是E=0 或 E=255?
c语言·嵌入式c·1024程序员节·单精度浮点数范围