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

目录

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),无额外内存消耗)。 |

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

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

相关推荐
猷咪14 小时前
C++基础
开发语言·c++
IT·小灰灰14 小时前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧14 小时前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q14 小时前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳014 小时前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾14 小时前
php 对接deepseek
android·开发语言·php
2601_9498683614 小时前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
yyy(十一月限定版)15 小时前
寒假集训4——二分排序
算法
星火开发设计15 小时前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
醉颜凉15 小时前
【LeetCode】打家劫舍III
c语言·算法·leetcode·树 深度优先搜索·动态规划 二叉树