欢迎光临
我们一直在努力

深入浅出插入排序与希尔排序:从原理到实战实现

深入浅出插入排序与希尔排序:从原理到实战实现

排序算法是数据结构与算法领域的基础核心内容,而插入类排序(插入排序、希尔排序)凭借其“增量有序”的核心思想,在小规模数据或近乎有序数据的排序场景中展现出独特的优势。本文将从底层原理、代码实现、性能分析等维度,深度拆解插入排序与希尔排序,帮你彻底吃透这两类经典排序算法。

一、插入排序:简单却实用的“整理手牌”思想

1. 核心原理:模拟手动整理手牌的过程

插入排序的核心逻辑可以类比我们玩扑克牌时整理手牌的动作:假设左手已经握有一组有序的牌,右手摸到新牌后,从右往左对比,将新牌插入到合适的位置,最终让整手牌保持有序。

算法层面的核心步骤:

  • 将数组分为“已排序区间”和“未排序区间”,初始时已排序区间只有第一个元素;
  • 遍历未排序区间的每个元素(记为temp),从已排序区间的末尾向前遍历;
  • 若已排序区间的元素大于temp,则将该元素向后移动一位;
  • 找到第一个小于等于temp的元素位置,将temp插入到该位置的下一位;
  • 重复上述步骤,直到未排序区间为空。
  • 2. 代码实现与解析

    结合本文的代码示例,插入排序的完整实现如下(对应Sort.c中InserSort函数):

    #include"Sort.h"

    // 插入排序
    void InserSort(int* arr, int n) {
    // 遍历未排序区间(i从0到n-2,对应未排序元素为arr[i+1])
    for (int i = 0; i < n 1; i++) {
    int end = i; // 已排序区间的末尾下标
    int temp = arr[end + 1]; // 待插入的未排序元素
    // 向前遍历已排序区间,找到插入位置
    while (end >= 0) {
    // 若已排序元素大于待插入元素,向后移动
    if (temp < arr[end]) {
    arr[end + 1] = arr[end];
    end;
    } else {
    // 找到插入位置,退出循环
    break;
    }
    }
    // 将待插入元素放入最终位置
    arr[end + 1] = temp;
    }
    }

    关键细节解析:

    • 循环边界:i < n – 1是因为每次处理的是arr[i+1],若i = n-1则i+1超出数组范围;
    • 临时变量temp:避免直接移动元素导致待插入值被覆盖;
    • end >= 0:确保遍历不会越界,若已排序区间所有元素都大于temp,则end最终为-1,arr[end+1]即arr[0],符合插入逻辑。

    3. 性能分析

    • 时间复杂度:
      • 最好情况(数组已有序):只需遍历一次,时间复杂度为O(n);
      • 最坏情况(数组逆序):每个元素都要遍历整个已排序区间,时间复杂度为O(n²);
      • 平均情况:O(n²)。
    • 空间复杂度:仅使用了临时变量,属于原地排序,空间复杂度为O(1);
    • 稳定性:插入排序是稳定排序(相等元素的相对位置不会改变)。

    4. 适用场景

    插入排序适合小规模数据排序或近乎有序的数组排序(比如数据库索引的局部调整、链表排序等),这也是为什么它常被作为希尔排序的“子排序”算法。

    二、希尔排序:插入排序的“优化升级版”

    插入排序在处理大规模无序数组时效率低下(O(n²)),而希尔排序(Shell Sort)通过“分组预排序”的思想,将数组先调整为“近乎有序”,再用插入排序收尾,大幅降低时间复杂度。

    1. 核心原理:缩小增量,逐步有序

    希尔排序的核心是“增量(gap)”:将数组按gap值划分为多个子数组(下标相差gap的元素为一组),对每个子数组分别进行插入排序;然后逐步缩小gap值,重复分组排序;当gap = 1时,数组已近乎有序,此时执行一次插入排序即可完成整体排序。

    算法层面的核心步骤:

  • 初始化增量gap(通常初始值为数组长度n);
  • 循环缩小gap(本文采用gap = gap / 3 + 1,确保最终gap能降到1);
  • 对每个gap,将数组分为gap个子数组,分别执行插入排序;
  • 当gap = 1时,完成最后一次插入排序,数组整体有序。
  • 2. 代码实现与解析

    对应Sort.c中ShellSort函数,完整实现如下:

    #include"Sort.h"

    // 希尔排序
    void ShellSort(int* arr, int n) {
    int gap = n; // 初始增量为数组长度
    // 当gap>1时,继续分组预排序;gap=1时执行最终插入排序
    while (gap > 1) {
    // 增量缩小策略:gap/3 +1,确保gap最终能到1
    gap = gap / 3 + 1;
    // 遍历每个子数组的未排序元素
    for (int i = 0; i < n gap; i++) {
    int end = i; // 子数组已排序区间末尾
    int temp = arr[end + gap]; // 子数组待插入元素
    // 子数组内的插入排序逻辑(与插入排序一致,步长为gap)
    while (end >= 0) {
    if (temp < arr[end]) {
    arr[end + gap] = arr[end];
    end -= gap;
    } else {
    break;
    }
    }
    arr[end + gap] = temp;
    }
    }
    }

    关键细节解析:

    • 增量策略:gap = gap / 3 + 1是一种经典的增量选择方式(也可选择gap /= 2),优势是能快速缩小增量,且避免gap出现0值;
    • 子数组排序:i < n – gap确保end + gap不越界,每个i对应一个子数组的起始位置,步长gap遍历子数组元素;
    • 与插入排序的关系:当gap = 1时,希尔排序的内层逻辑完全等同于插入排序,此时数组已通过多次预排序变得近乎有序,插入排序的效率会大幅提升。

    3. 性能分析

    希尔排序的时间复杂度分析较为复杂,与增量选择密切相关:

    • 时间复杂度:
      • 最好情况:O(n^1.3)(远优于插入排序的O(n));
      • 最坏情况:约O(n²)(若增量选择不当);
      • 平均情况:约O(n^1.3)~O(n^1.5);
    • 空间复杂度:原地排序,O(1);
    • 稳定性:不稳定排序(分组排序时,相等元素可能被分到不同组,导致相对位置改变)。

    4. 适用场景

    希尔排序适合中等规模的数组排序,在数据量介于几百到几万之间时,效率优于冒泡、插入等基础排序算法;相比快速排序、归并排序,希尔排序实现更简单,空间开销更小。

    三、实战测试:验证排序效果

    本文的test.c提供了测试逻辑,我们可以通过以下步骤验证两种排序算法的效果:

    1. 测试代码解析

    #include"Sort.h"

    // 打印数组
    void apprintf(int* arr, int n) {
    for (int i = 0; i < n; i++) {
    printf("%d ", arr[i]);
    }
    printf("\\n");
    }

    void test1() {
    // 测试数组
    int arr[] = { 2,5,6,9,3,1,4,8,7 };
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("原数组:");
    apprintf(arr, n);

    // 测试插入排序
    // InserSort(arr, n);
    // printf("插入排序后:");
    // apprintf(arr, n);

    // 测试希尔排序
    ShellSort(arr, n);
    printf("希尔排序后:");
    apprintf(arr, n);
    }

    int main() {
    test1();
    return 0;
    }

    2. 测试结果

    • 原数组:2 5 6 9 3 1 4 8 7;
    • 希尔排序后:1 2 3 4 5 6 7 8 9;
    • 若取消注释测试插入排序,结果同样为有序数组。

    四、插入排序 vs 希尔排序:核心对比

    特性插入排序希尔排序
    时间复杂度 最好O(n),最坏O(n²) 约O(n^1.3)
    稳定性 稳定 不稳定
    空间复杂度 O(1) O(1)
    适用数据规模 小规模/近乎有序 中等规模
    核心思想 直接插入 分组预排序+插入

    五、总结

    插入排序是插入类排序的基础,其“增量有序”的思想简单易懂,在小规模数据场景中实用性强;希尔排序通过“缩小增量”的优化,解决了插入排序在大规模无序数据中效率低的问题,是对插入排序的经典升级。

    掌握这两种算法,不仅能理解“简单算法优化”的思路,更能在实际开发中根据数据规模和有序程度,选择合适的排序方案——比如小规模数据用插入排序,中等规模数据用希尔排序,大规模数据则可选择快速排序、归并排序等更高效的算法。

    从原理到代码,从性能到场景,吃透插入排序与希尔排序,是夯实数据结构与算法基础的重要一步。

    赞(0)
    未经允许不得转载:171主机测评 » 深入浅出插入排序与希尔排序:从原理到实战实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址