• 排序算法——归并排序以及非递归实现


    一、归并排序思想

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

    我们现在要树立这么一个思路,分解数组并不是真的分解数组,而是可以通过下标的形式来区分数组。归并排序实际上就是将一个数组分解到不能子啊分解的部分,两两经行比较排序,再将数据更新到新的数组中去。这非常类似于我们学的数的后序遍历。

    二、代码实现

    1、单趟:

    我们先写单趟,因为归并排序是对半分组,所以最后为两个区间经行比较,思路转化为之前我们学过的两个数组的合并

    1. int mid = (rigt + left) / 2;
    2. int begin1 = left;
    3. int end1 = mid-1;
    4. int begin2 = mid;
    5. int end2 = rigt;
    6. int i = left;
    7. while (begin1 <= end1 && begin2 <= end2)
    8. {
    9. if (a[begin1] > a[begin2])
    10. {
    11. tmp[i++] = a[begin2++];
    12. }
    13. else
    14. {
    15. tmp[i++] = a[begin1++];
    16. }
    17. }
    18. while (begin1<=end1)
    19. {
    20. tmp[i++] = a[begin1++];
    21. }
    22. while (begin2 <= end2)
    23. {
    24. tmp[i++] = a[begin2++];
    25. }
    26. memcpy(a+left, tmp+left, sizeof(int) * (rigt - left + 1));

    这里要注意两个问题:1、区间怎么划分合适,2、为什么每排序一次就得将数组拷贝回去

     2、单趟问题解释

    首先我们先解决第一个问题:区间怎么划分合适?

    我们这里以下面图为例:

    因为有-1的存在很容易导致下标为负数,最后导致数组的违法访问。同时可能还会有死循环的问题:

    具体原因如下:

    但是如果改为区间为 

    int begin1 = left;
    int end1 = mid;
    int begin2 = mid+1;
    int end2 = rigt;就不会出现这个问题:

    所以正确代码如下:

    1. int mid = (rigt + left) / 2;
    2. int begin1 = left;
    3. int end1 = mid;
    4. int begin2 = mid+1;
    5. int end2 = rigt;
    6. int i = left;
    7. while (begin1 <= end1 && begin2 <= end2)
    8. {
    9. if (a[begin1] > a[begin2])
    10. {
    11. tmp[i++] = a[begin2++];
    12. }
    13. else
    14. {
    15. tmp[i++] = a[begin1++];
    16. }
    17. }
    18. while (begin1<=end1)
    19. {
    20. tmp[i++] = a[begin1++];
    21. }
    22. while (begin2 <= end2)
    23. {
    24. tmp[i++] = a[begin2++];
    25. }
    26. memcpy(a+left, tmp+left, sizeof(int) * (rigt - left + 1));

    第二个问题:因为比较的时候是在原数组经行比较如果不及时将数组内容更新,可能会导致再排序时经行错误的大小比较,最后导致结果错误 

    3、总体代码实现

    结束条件当分割区间等于1或者小于1的时候。

    1. void _MergeSort(int* a, int* tmp, int left, int rigt)
    2. {
    3. if (left >= rigt)
    4. {
    5. return;
    6. }
    7. int mid = (rigt + left) / 2;
    8. _MergeSort(a, tmp, left, mid);
    9. _MergeSort(a, tmp, mid + 1, rigt);
    10. int begin1 = left;
    11. int end1 = mid;
    12. int begin2 = mid+1;
    13. int end2 = rigt;
    14. int i = left;
    15. while (begin1 <= end1 && begin2 <= end2)
    16. {
    17. if (a[begin1] > a[begin2])
    18. {
    19. tmp[i++] = a[begin2++];
    20. }
    21. else
    22. {
    23. tmp[i++] = a[begin1++];
    24. }
    25. }
    26. while (begin1<=end1)
    27. {
    28. tmp[i++] = a[begin1++];
    29. }
    30. while (begin2 <= end2)
    31. {
    32. tmp[i++] = a[begin2++];
    33. }
    34. memcpy(a+left, tmp+left, sizeof(int) * (rigt - left + 1));
    35. }
    36. void MergeSort(int* a, int n)
    37. {
    38. int* tmp = (int*)malloc(sizeof(int) * n);
    39. if (tmp == NULL)
    40. {
    41. return;
    42. }
    43. else
    44. {
    45. _MergeSort(a, tmp, 0, n - 1);
    46. free(tmp);
    47. tmp = NULL;
    48. }
    49. }

    三、非递归

    对于归并排序我们实现非递归用循环更好一点,非递归我们可以直接从递归回去那出发,定义一个组个数,通过控制组的个数实现“并”这个过程:

    还是我们先写一个数组只有一个数的情况:

    1. for (int i = 0; i < end - begin + 1; i += 2 * gap)//每次比较两组,所以加2gap
    2. {
    3. int begin1 = i;
    4. int end1 = begin1 + gap - 1;
    5. int begin2 = begin1 + 2 * gap - 1;//第二组的开头与第一组的开头差2倍
    6. int end2 = begin2 + gap - 1;
    7. int k = i;
    8. while (begin1 <= end1 && begin2 <= end2)
    9. {
    10. if (a[begin1] > a[begin2])
    11. {
    12. tmp[k++] = a[begin2++];
    13. }
    14. else
    15. {
    16. tmp[k++] = a[begin1++];
    17. }
    18. }
    19. while (begin1 <= end1)
    20. {
    21. tmp[k++] = a[begin1++];
    22. }
    23. while (begin2 <= end2)
    24. {
    25. tmp[k++] = a[begin2++];
    26. }
    27. memcpy(a + i, tmp + i, sizeof(int) * (2 * gap));//同理这里也得排序一次就拷贝回去
    28. }

    那么后面就是gap*2控制即可:

    1. void _MergeSortNonR(int* a, int* tmp,int begin, int end)
    2. {
    3. int gap = 1;
    4. for (int j = gap; j < end-begin+1; j *= 2)
    5. {
    6. for (int i = 0; i < end - begin + 1; i += 2 * gap)//每次比较两组,所以加2gap
    7. {
    8. int begin1 = i;
    9. int end1 = begin1 + gap - 1;
    10. int begin2 = begin1 + 2 * gap - 1;//第二组的开头与第一组的开头差2倍
    11. int end2 = begin2 + gap - 1;
    12. int k = i;
    13. while (begin1 <= end1 && begin2 <= end2)
    14. {
    15. if (a[begin1] > a[begin2])
    16. {
    17. tmp[k++] = a[begin2++];
    18. }
    19. else
    20. {
    21. tmp[k++] = a[begin1++];
    22. }
    23. }
    24. while (begin1 <= end1)
    25. {
    26. tmp[k++] = a[begin1++];
    27. }
    28. while (begin2 <= end2)
    29. {
    30. tmp[k++] = a[begin2++];
    31. }
    32. memcpy(a + i, tmp + i, sizeof(int) * (2 * gap));
    33. }
    34. }
    35. }

    但是上面的代码仍然存在问题,和快排类似这里会存在数组越界问题:这里我们打印数组下标区间来观察一下:、

    大致我们可以分为这两种情况:

     如果end1越界,那么说明分为了只有这一组数据,不用排了直接跳出循环,如果end2越界我们就需要给end2修改值。

    所以改进代码如下:

    1. void _MergeSortNonR(int* a, int* tmp,int begin, int end)
    2. {
    3. int gap = 1;
    4. for (int j = gap; j < end-begin+1; j *= 2)
    5. {
    6. for (int i = 0; i < end - begin + 1; i += 2 * gap)//每次比较两组,所以加2gap
    7. {
    8. int begin1 = i;
    9. int end1 = begin1 + gap - 1;
    10. int begin2 = begin1 + 2 * gap - 1;//第二组的开头与第一组的开头差2倍
    11. int end2 = begin2 + gap - 1;
    12. int k = i;
    13. // 第二组都越界不存在,这一组就不需要归并
    14. if (begin2 >= end - begin + 1)
    15. break;
    16. // 第二的组begin2没越界,end2越界了,需要修正一下,继续归并
    17. if (end2 >= end - begin + 1)
    18. end2 = end - begin + 1 - 1;
    19. while (begin1 <= end1 && begin2 <= end2)
    20. {
    21. if (a[begin1] > a[begin2])
    22. {
    23. tmp[k++] = a[begin2++];
    24. }
    25. else
    26. {
    27. tmp[k++] = a[begin1++];
    28. }
    29. }
    30. while (begin1 <= end1)
    31. {
    32. tmp[k++] = a[begin1++];
    33. }
    34. while (begin2 <= end2)
    35. {
    36. tmp[k++] = a[begin2++];
    37. }
    38. memcpy(a + i, tmp + i, sizeof(int) * (2 * gap));
    39. }
    40. }
    41. }
    42. void MergeSortNonR(int* a, int n)
    43. {
    44. int* tmp = (int*)malloc(sizeof(int) * n);
    45. if (tmp == NULL)
    46. {
    47. return;
    48. }
    49. else
    50. {
    51. _MergeSortNonR(a, tmp, 0, n - 1);
    52. free(tmp);
    53. tmp = NULL;
    54. }
    55. }

    可能有人会想如果只剩一组数据了,我直接return行不行?实际上是可以的,因为break跳出循环后在gap*2还是只有一组数据,所以直接return即可。

  • 相关阅读:
    智能运维之告警聚合技术介绍
    使用CompletableFuture多线程异步任务优化查询接口性能
    C++入门和基础
    从一次Kafka宕机说起(JVM hang)
    docker学习(一)
    static的作用
    C++虚函数(定义,作用,原理,案例)
    手把手教你如何玩转Git
    java网络通信
    Pass cfg from cmd to test
  • 原文地址:https://blog.csdn.net/suiyi_freely/article/details/139423128