当前位置:   article > 正文

归并排序详解(递归与非递归)

归并排序详解(递归与非递归)

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

一、递归实现归并排序

1.算法描述

● 把长度为n的输入序列分成两个长度近似为n/2([begin,mid]  [mid + 1,end] )的子序列

● 对这两个子序列再分别进行归并排序,将区间分解到不可在分为止

● 依次将两个排序好的子序列合并成一个最终的排序序列 

方法: 开辟新数组,用双指针选出两个子序列中的较小元素尾插在新数组中

 2.动图演示

3.核心步骤 

(1)图示

(2)思考

为什么要将区间拆分成[begin,mid]  [mid + 1,end] ?

上面介绍过我们的算法步骤是把长度为 n 的输入序列分成两个长度近似为 n/2 的子序列,在此前提下,除了分成[begin,mid]  [mid + 1,end]外,还有一种分法:[begin,mid-1] [mid,end],为什么不用这种分法?

mid = (begin + end)/2,所以mid是偶数

示例:用两种不同的分法分解区间[0,9]

当所要分解的区间是 [偶数,偶数],上述的两种分法都行得通,但是当区间是[偶数,偶数+1]的情况,[begin,mid-1] [mid,end]这种分法就行不通。

5.参考代码

  1. void _MergeSort(int* arr, int* tmp, int begin, int end)
  2. {
  3. if (begin >= end)
  4. return;
  5. int mid = (begin + end) / 2;//偶数
  6. //[begin,mid] [mid+1,end] ——>[偶数,偶数] [偶数+1,偶数+1]
  7. _MergeSort(arr,tmp, begin, mid);
  8. _MergeSort(arr, tmp, mid + 1, end);
  9. //左右区间有序就可以归并了
  10. //归并
  11. int begin1 = begin, end1 = mid;
  12. int begin2 = mid + 1, end2 = end;
  13. int i = begin;
  14. while (begin1 <= end1 && begin2 <= end2)
  15. {
  16. if (arr[begin1] < arr[begin2])
  17. {
  18. tmp[i++] = arr[begin1++];
  19. }
  20. else
  21. {
  22. tmp[i++] = arr[begin2++];
  23. }
  24. }
  25. //剩下的尾插在tmp数组
  26. while (begin1 <= end1)
  27. {
  28. tmp[i++] = arr[begin1++];
  29. }
  30. while (begin2 <= end2)
  31. {
  32. tmp[i++] = arr[begin2++];
  33. }
  34. //将一定长度的tmp复制到arr中
  35. memcpy(arr + begin, tmp + begin, (end - begin + 1) * sizeof(int));
  36. }
  37. //归并
  38. void MergeSort(int* arr, int n)
  39. {
  40. int* tmp = (int*)malloc(sizeof(int) * n);
  41. if (tmp == NULL)
  42. {
  43. perror("malloc fail!");
  44. return;
  45. }
  46. _MergeSort(arr,tmp, 0, n - 1);
  47. free(tmp);
  48. tmp = NULL;
  49. }

不同排序对一百万个随机数进行排序的效率: (毫秒)

二、非递归实现归并排序

1.算法描述

● gap表示每组归并的数据个数

● i 表示每组归并的起始位置

● 两层循环,一层循环用来归并数组中的数据,另一层扩大归并的数据个数

2.核心步骤

(1)图示

 (2)思考

为什么要归并一次拷贝一次?

● 如果整体拷贝,在上述的“第二组的都越界不存在”的情况下,虽然跳出循环,但在整体拷贝的时候还会把越界的部分拷贝回去

● 但如果归并一次拷贝一次,在“第二组的都越界不存在”的情况下,直接跳出循环,不会将越界的部分拷贝到数组中

3.参考代码 

  1. //归并(非递归)
  2. void MergeSortNonR(int* arr, int n)
  3. {
  4. int* tmp = (int*)malloc(sizeof(int) * n);
  5. if (tmp == NULL)
  6. {
  7. perror("malloc fail!");
  8. return;
  9. }
  10. //gap表示每组要归并数据的个数
  11. int gap = 1;
  12. while (gap < n)
  13. { //i表示每组归并的起始位置
  14. for (int i = 0; i < n; i += 2 * gap)//个数于区间的转化关系
  15. {//[bagin1,end1] [bagin2,end2]
  16. int begin1 = i, end1 = i + gap - 1;
  17. int begin2 = i + gap, end2 = i + 2 * gap - 1;
  18. //第二组的都越界不存在,那么第二组不用归并了,跳出循环
  19. if (begin2 > n)
  20. break;
  21. //第二组的begin2没越界,end2越界了,需要修正区间,再归并
  22. if (end2 > n)
  23. end2 = n - 1;
  24. //归并
  25. int j = i;
  26. while (begin1 <= end1 && begin2 <= end2)
  27. {
  28. if (arr[begin1] < arr[begin2])
  29. {
  30. tmp[j++] = arr[begin1++];
  31. }
  32. else
  33. {
  34. tmp[j++] = arr[begin2++];
  35. }
  36. }
  37. //剩下的尾插在tmp数组
  38. while (begin1 <= end1)
  39. {
  40. tmp[j++] = arr[begin1++];
  41. }
  42. while (begin2 <= end2)
  43. {
  44. tmp[j++] = arr[begin2++];
  45. }
  46. //将一定长度的tmp复制到arr中
  47. //归并一次拷贝一次
  48. memcpy(arr + i, tmp + i, (end2 - i + 1) * sizeof(int));
  49. }
  50. gap *= 2;
  51. }
  52. free(tmp);
  53. tmp = NULL;
  54. }

三、算法分析 

归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(N*logN),代价是需要额外的内存空间。

特性总结:

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

(2) 时间复杂度:O(N*logN)  相当于一个满二叉树

(3) 空间复杂度:O(N)  开辟了一个额外的数组

(4) 稳定性:稳定

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/786968
推荐阅读
相关标签
  

闽ICP备14008679号