• 排序算法之归并排序(递归和非递归版)


     c556452bb2e641388ff5cbf0ad0cd087.png 个人主页:欢迎大家光临——>沙漠下的胡杨

    8677fe6ae6e446eba7e0203a3f198059.png  各位大帅哥,大漂亮

    182b5d086f4c440aa67d6a3f6747f3ca.png 如果觉得文章对自己有帮助

    d1ec8894bf4b4d31ab1a86a58ea34714.png 可以一键三连支持博主

    7b7d3b82439b40c48b5e0aa5e1468a51.png 你的每一分关心都是我坚持的动力

     

    b9890a16ced94dcc95130f5480d21937.gif ebd7b09fe3334c569c31567a87064844.gif

     ☄: 本期重点:排序算法的递归和非递归实现

      希望大家每天都心情愉悦的学习工作。 


    目录

     ☄: 本期重点:排序算法的递归和非递归实现

    归并排序思想:

    代码实现:

    归并的非递归

    归并排序特点:


     

     

    4b310d430f5949498b48b1164c49a626.jpeg

     

    归并排序思想:

    归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

     

    递归的思想:

    简单而言就是把原来的范围分割为两个区间,然后让左区间有序,右区间有序,进行归并即可,但是此时的左右区间还没有有序,我们就要在进行拆分,直到区间内元素个数为一时停止,这个部分我们可以叫做拆分的过程。

    5446383c883a4f4ea6dd2f1f4b823b6a.png

     下面进行一一的合并,就是归到一个数组。

    da6c7f9fdf154b56bae7f6c2813dcdf9.png

    此时的数组就是有序的。 

    代码实现:

    递归时,我们实际的流程如下图所示:

    首先,我们先把区间分割开。

    098f418659dd4a6cbedd43e891ffc607.png

     然后,利用临时数组进行归并

    d396009bb393403d9c4c358b09437017.png

     58a0ae5ba2d447239a16ba31ffad9327.png

    e3d8c17e21b84ca28cbf07eca890c2bf.png

     

    递归代码实现:

    1. void _Merge(int*a, int*tmp, int begin1, int end1, int begin2, int end2)
    2. {
    3. //定义两个循环变量
    4. int i = begin1;
    5. int j = begin1;
    6. //进行循环,结束条件为两个区间中一个结束
    7. while (begin1 <= end1 && begin2 <= end2)
    8. {
    9. if (a[begin1] < a[begin2])
    10. tmp[i++] = a[begin1++];
    11. else
    12. tmp[i++] = a[begin2++];
    13. }
    14. //把没有走完的区间插入到后面
    15. //有两个循环,但是只会走一个
    16. while (begin1 <= end1)
    17. {
    18. tmp[i++] = a[begin1++];
    19. }
    20. while (begin2 <= end2)
    21. {
    22. tmp[i++] = a[begin2++];
    23. }
    24. //最后把归并完的结果拷贝回原数组
    25. for (; j <= end2; ++j)
    26. {
    27. a[j] = tmp[j];
    28. }
    29. }
    30. void _MergeSort(int* a, int left, int right, int* tmp)
    31. {
    32. //如果区间只有一个元素就停止
    33. if (left >= right)
    34. {
    35. return;
    36. }
    37. //取区间的中间,分为两个区间
    38. int mid = (right + left) >> 1;
    39. //分别递归左区间和右区间
    40. _MergeSort(a, left, mid, tmp);
    41. _MergeSort(a, mid+1, right, tmp);
    42. //此时的区间要么已经有序了,要么区间内只有一个元素。
    43. int begin1 = left, end1 = mid;
    44. int begin2 = mid+1, end2 = right;
    45. //下标从left,左区间的左边开始
    46. //归并部分的代码
    47. _Merge(a, tmp, begin1, end1, begin2, end2);
    48. }
    49. void MergeSort(int* a, int n)
    50. {
    51. //开辟临时数组
    52. int* tmp = (int *)malloc(sizeof(int)*n);
    53. if (tmp == NULL)
    54. {
    55. perror("MergeSort: malloc fail");
    56. exit(-1);
    57. }
    58. //排序的子函数,因为不能每次都开辟临时数组
    59. _MergeSort(a, 0, n - 1, tmp);
    60. //释放数组空间,并置为NULL
    61. free(tmp);
    62. tmp = NULL;
    63. }

    2e30117ef86945009225e48a64df53d5.gif

     

     

    归并的非递归

    归并排序的非递归我们可以看做是原数组直接进行合并的过程,不需要拆分,每次只需要控制循环时的区间内的元素个数即可。

     

    我们先数据一个一个进行归并,在拷贝回原数组

    5d934c3dfe2549c2ac377e163b7dccaa.png

     在两个两个归并,拷贝回原数组。

    dd8e75219d8840598a6d19374b604361.png 

     最后四个四个归并,拷贝回原数组

    3bda879c946445d89f8d6194d1250c45.png

    在这个过程中我们要考虑三种情况:

    1.第一个区间不够 gap 个元素

    2.没有第二个区间,只有一个区间。

    3.第二个区间不够 gap个元素

     

    其中第一种和第二种我们可以不用进行归并了,直接跳出本轮循环,第三种情况是越界行为我们要修正最后的范围。

     

    代码如下:
     

    1. void _Merge(int*a, int*tmp, int begin1, int end1, int begin2, int end2)
    2. {
    3. //定义两个循环变量
    4. int i = begin1;
    5. int j = begin1;
    6. //进行循环,结束条件为两个区间中一个结束
    7. while (begin1 <= end1 && begin2 <= end2)
    8. {
    9. if (a[begin1] < a[begin2])
    10. tmp[i++] = a[begin1++];
    11. else
    12. tmp[i++] = a[begin2++];
    13. }
    14. //把没有走完的区间插入到后面
    15. //有两个循环,但是只会走一个
    16. while (begin1 <= end1)
    17. {
    18. tmp[i++] = a[begin1++];
    19. }
    20. while (begin2 <= end2)
    21. {
    22. tmp[i++] = a[begin2++];
    23. }
    24. //最后把归并完的结果拷贝回原数组
    25. for (; j <= end2; ++j)
    26. {
    27. a[j] = tmp[j];
    28. }
    29. }
    30. void MergeSortNonR(int* a, int n)
    31. {
    32. //开辟临时数组
    33. int* tmp = (int *)malloc(sizeof(int)*n);
    34. if (tmp == NULL)
    35. {
    36. perror("MergeSort: malloc fail");
    37. exit(-1);
    38. }
    39. //区间的步长
    40. int gap = 1;
    41. //保证步长不大于数据范围
    42. while (gap < n)
    43. {
    44. //每个步长下,归并多少次
    45. for (int i = 0; i < n; i += 2*gap)
    46. {
    47. //保存左右区间的开始结束
    48. int begin1 = i, end1 = i + gap - 1, begin2 = i + gap, end2 = i + 2 * gap - 1;
    49. //如果右区间的开始处大于总的数据个数,第一区间不够gap个
    50. //或者不存在第二个区间,不进行归并,直接跳出循环
    51. if (begin2 >= n)
    52. {
    53. break;
    54. }
    55. //如果右区间的结束处大于数据个数,
    56. //表示第二个区间数据量不够gap个,越界了,修改结束位置。
    57. if (end2 >= n)
    58. {
    59. end2 = n-1;
    60. }
    61. //归并部分的代码
    62. _Merge(a, tmp, begin1, end1, begin2, end2);
    63. }
    64. //每次循环结束,gap要变为2倍
    65. gap *= 2;
    66. }
    67. //释放空间。
    68. free(tmp);
    69. tmp = NULL;
    70. }

    归并排序特点:

    1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

    2. 时间复杂度:O(N*logN)

    3. 空间复杂度:O(N)

    4. 稳定性:稳定

     

     

  • 相关阅读:
    【Educoder数据挖掘实训】异常值检测-箱线图
    “深入理解C++类默认成员函数:探索构造、析构与复制“
    【JavaScript 逆向】极验四代无感验证码逆向分析
    学信息系统项目管理师第4版系列30_信息系统管理
    cookie以及Storage的共同点、区别与使用
    华为云云服务器评测|详解 Nacos 安装部署
    鼠标悬停效果八
    激活码问题解疑【点盾云】
    信息系统项目管理师:软件测试、调试及其管理
    Yolov8小目标检测(9): EMA基于跨空间学习的高效多尺度注意力、效果优于ECA、CBAM、CA | ICASSP2023
  • 原文地址:https://blog.csdn.net/m0_64770095/article/details/125562430