• 【数据结构初阶】C语言从0到1实现希尔排序


     🌟hello,各位读者大大们你们好呀🌟

    🍭🍭系列专栏:【数据结构初阶】

    ✒️✒️本篇内容:深入剖析希尔排序

    🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-

    📡📡同期数据结构文章:【数据结构初阶】C语言从0到1带你了解直接插入排序

    前言

    需要直接插入排序的基础,才能更好的理解文章,详情可见上栏同期数据结构文章

    希尔排序法又称缩小增量法,希尔排序法的基本思想是:

    先选定一个整数,把待排序文件中所有数分成 gap 个组,所有距离为 gap 的数分在同一组内,并对每一组内的数先进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

    1.先进行预排序,将一组数分为 gap 组,对间隔为 gap 的数进行直接插入排序

    1. void ShellSort(int* a, int n)
    2. {
    3. int gap;
    4. int end;
    5. int tmp = a[end + gap];
    6. while (end >= 0)
    7. {
    8. if (a[end] > tmp)
    9. {
    10. a[end + gap] = a[end];
    11. end -= gap;
    12. }
    13. else
    14. {
    15. break;
    16. }
    17. }
    18. a[end + gap] = tmp;
    19. }

    至此,我们已经完成了一次插入排序,也就是可以将红色组的9、5变得有序。接下来就是加入循环,完成多次插入排序,使间隔为 gap 的数组成的数组有序,以下图红色组为例,就是将 9,5,8,5 变得有序


     2.加入循环,完成多次插入排序,使间隔为 gap 的数组成的数组有序

    • 增加一个for循环,对红色组进行排序
    • n为数组长度,i < n - gap 避免越界(若 i
    1. int gap = 3;
    2. for (int i = 0; i < n - gap; i += gap)
    3. {
    4. // [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
    5. int end = i;
    6. int tmp = a[end + gap];
    7. while (end >= 0)
    8. {
    9. if (a[end] > tmp)
    10. {
    11. a[end + gap] = a[end];
    12. end -= gap;
    13. }
    14. else
    15. {
    16. break;
    17. }
    18. }
    19. a[end + gap] = tmp;
    20. }

    至此,我们第一组(红色组)就排完了,数组将会变成 5,1,2,5,7,4,8,6,3,9


    3.再加一组循环,循环 gap 次,使 gap 组的数都变得有序(以下数组为例,就是让红、蓝、绿组都变得有序)

    • 创建一个for循环,对不同颜色组排序
    • 在这里,j = 0,排红色组;j = 1,排蓝色组;j = 2,排绿色组

    1. void ShellSort(int* a, int n)
    2. {
    3. int gap = 3;
    4. for (int j = 0; j < gap; ++j)
    5. {
    6. for (int i = j; i < n - gap; i += gap)
    7. {
    8. // [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
    9. int end = i;
    10. int tmp = a[end + gap];
    11. while (end >= 0)
    12. {
    13. if (a[end] > tmp)
    14. {
    15. a[end + gap] = a[end];
    16. end -= gap;
    17. }
    18. else
    19. {
    20. break;
    21. }
    22. }
    23. a[end + gap] = tmp;
    24. }
    25. }


    4.算法优化,三层循环变两层循环(时间复杂度相同,所以写上面的这种也行)

    • 只需要将第二层循环 i += gap 变为  ++i 即可,可以理解红组,然后红组9,5拍完,蓝组1,7排,蓝组排完绿组2,4排,以此类推。
    • 不同于上面先给红组排完,再给蓝组排,蓝组排完,再给绿组排

    1. void ShellSort(int* a, int n)
    2. {
    3. int gap = 3;
    4. for (int i = j; i < n - gap; ++i)
    5. {
    6. // [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
    7. int end = i;
    8. int tmp = a[end + gap];
    9. while (end >= 0)
    10. {
    11. if (a[end] > tmp)
    12. {
    13. a[end + gap] = a[end];
    14. end -= gap;
    15. }
    16. else
    17. {
    18. break;
    19. }
    20. }
    21. a[end + gap] = tmp;
    22. }
    23. }

    至此,我们已经完成了预排序, 完成了数组接近有序的目标,大大降低了对每个数直接排序的时间复杂度


    5.对每个数进行直接插入排序,我们可以通过控制 gap 的值,来区分预排序和直接插入排序

    • 当 gap == 1 时,这个算法就是直接插入排序
    • 我们可以设 gap = n,再通过循环,不断缩小gap的值,不断预排序,使其最后变为直接插入排序
    • 通过大量实验我们可以得出,gap / 2,或者 gap / 3+1 时,预排序的效果最好
    • gap / 3+1 是为了保证除到最后的数=1,如3/3+1=1,4/3+1=2,2/3+1=1
    1. // gap > 1 预排序
    2. // gap == 1 直接插入排序
    3. // O(N^1.3)
    4. void ShellSort(int* a, int n)
    5. {
    6. int gap = n;
    7. while (gap > 1)
    8. {
    9. //gap = gap / 2;
    10. gap = gap / 3 + 1; //+1是为了保证除到最后的数=1
    11. // [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
    12. for (int i = 0; i < n - gap; ++i)
    13. {
    14. int end = i;
    15. int tmp = a[end + gap];
    16. while (end >= 0)
    17. {
    18. if (a[end] > tmp)
    19. {
    20. a[end + gap] = a[end];
    21. end -= gap;
    22. }
    23. else
    24. {
    25. break;
    26. }
    27. }
    28. a[end + gap] = tmp;
    29. }
    30. }
    31. }


    6.我们将上述代码补充完整,进行一次实验

    sort.h

    1. #include
    2. #include
    3. void ShellSort(int* a, int n);

     sort.c

    1. void PrintArray(int* a, int n)
    2. {
    3. for (int i = 0; i < n; ++i)
    4. {
    5. printf("%d ", a[i]);
    6. }
    7. printf("\n");
    8. }
    9. // gap > 1 预排序
    10. // gap == 1 直接插入排序
    11. // O(N^1.3)
    12. void ShellSort(int* a, int n)
    13. {
    14. int gap = n;
    15. while (gap > 1)
    16. {
    17. //gap = gap / 2;
    18. gap = gap / 3 + 1; //+1是为了保证除到最后的数=1
    19. // [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
    20. for (int i = 0; i < n - gap; ++i)
    21. {
    22. int end = i;
    23. int tmp = a[end + gap];
    24. while (end >= 0)
    25. {
    26. if (a[end] > tmp)
    27. {
    28. a[end + gap] = a[end];
    29. end -= gap;
    30. }
    31. else
    32. {
    33. break;
    34. }
    35. }
    36. a[end + gap] = tmp;
    37. }
    38. }
    39. }

    test.c

    1. void TestShellSort()
    2. {
    3. //int a[] = { 9,8,7,6,5,4,3,2,1,0 };
    4. int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
    5. ShellSort(a, sizeof(a) / sizeof(int));
    6. PrintArray(a, sizeof(a) / sizeof(int));
    7. }
    8. int main()
    9. {
    10. TestShellSort();
    11. return 0;
    12. }

    结果如下  


    希尔排序的特性总结:

    1. 希尔排序是对直接插入排序的优化。
    2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
    3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定,我们可以使用Knuth的取值或者大概平均值O(n^1.3),对比下略差于O(n*logn)的算法
    4. 稳定性:不稳定

    《数据结构-用面相对象方法与C++描述》--- 殷人昆


    🌹🌹希尔排序排序大概就讲到这里啦,博主后续会继续更新更多数据结构的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪 

  • 相关阅读:
    超好用的数据库检索工具介绍——Bean Searcher
    地图数据设计(二):矢量数据检查与错误处理
    【liunx】yum&&vim
    设计模式—— 工厂方法模式(Factory Pattern)+ Spring相关源码
    上周热点回顾(4.24-4.30)
    iso27001信息安全体系认证咨询
    CUDA编程入门系列(十)并行规约
    【MLT】MLT多媒体框架生产消费架构解析(三)
    Python数据分析将数组的多行元素首尾相连为一行numpy.ravel()
    接口测试(jmeter和postman 接口使用)
  • 原文地址:https://blog.csdn.net/Captain_ldx/article/details/127956057