当前位置:   article > 正文

数据结构——排序(4):归并排序+计数排序

数据结构——排序(4):归并排序+计数排序

目录

一、归并排序

(1)思想

(2)过程图示

(3)代码实现

 (4) 代码解释

(5)复杂度

二、非比较排序(计数排序)

(1)操作步骤

(2)图示

(3)思考

(4)代码实现

(5)注意 

(6)代码解释

(7)复杂度

三、排序性能对比

四、排序算法特性

稳定性验证案例

五、写在最后


一、归并排序

(1)思想

归并排序是建立在归并上的一种排序算法,该算法是采用分治法的一个典型应用。

将已有序的子序列合并,得到完全有序的序列(即先使每一个子序列有序,在使子序列段间有序)。若将两个有序表合并成一个有序表,称为二路归并

(2)过程图示

分解:类似于二叉树的结构,将子序列从中间分为左右序列[left , mid]、[mid + 1 , right],直至不能再分解。

合并:将两个子序列排好序合并在一起。

(3)代码实现

  1. void _MergeSort(int* arr, int left, int right, int* tmp)
  2. {
  3. if(left >= right)
  4. {
  5. return;
  6. }
  7. //分解
  8. int mid = (left + right) / 2;
  9. //左序列
  10. _MergeSort(arr, left, mid, tmp);
  11. //右序列
  12. _MergeSort(arr, mid + 1, right, tmp);
  13. //左序列
  14. int begin1 = left;
  15. int end1 = mid;
  16. //右序列
  17. int begin2 = mid + 1;
  18. int end2 = right;
  19. int index = begin1;
  20. //合并
  21. while(begin1 <= end1 && begin2 <= end2)
  22. {
  23. if(arr[begin1] < arr[begin2])
  24. {
  25. tmp[index++] = arr[begin1++];
  26. }
  27. else
  28. {
  29. tmp[index] = arr[begin2++];
  30. }
  31. }
  32. //begin1越界
  33. while(begin1 <= end1)
  34. {
  35. tmp[index++] = arr[begin1];
  36. }
  37. //或者begin2越界
  38. while(begin2 <= end2)
  39. {
  40. tmp[index++] = arr[begin2];
  41. }
  42. //根据tmp数组更新arr数组
  43. for(int i = left; i <= right; i ++)
  44. {
  45. arr[i] = tmp[i];
  46. }
  47. }
  48. void MergeSort(int* arr, int n)
  49. {
  50. int* tmp = (int*)malloc(sizeof(int)*n);
  51. _MergeSort(arr, 0, n - 1, tmp);
  52. free(tmp);
  53. }

 (4) 代码解释

1.首先,我们来看主函数MergeSort():创建一个与原数组等大小的数组tmp,然后进行归并排序。

2.接着,在_MergeSort中:

分解时,我们通过下标找到该序列的left和right,那么就可以找到左子序列[left , mid]和右子序列[mid + 1 , right],然后递归左右子序列,直至left>=right,结束递归。

合并时,我们创建变量begin1、end1、begin2、end2来表示左右子序列的两端,用index来表示tmp数组的下标。在两个序列中,我们将其中的数据进行排序。当跳出循环时,说明左子序列遍历完成或者右子序列遍历完成,将剩下的序列中的数据存放入tmp序列中即可。至此我们完成了合并。

3.完成了排序,此时排列好的数据仍然存放在tmp数组中,我们根据tmp将arr数组进行更新,到此就完成了整个归并排序。


(5)复杂度

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

2.空间复杂度:O(N)。


二、非比较排序(计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

(1)操作步骤

1.统计出每个元素出现的次数;

2.将统计的结果保存在数组中。

(2)图示

例如:数组{6,1,2,9,4,2,4,1,1}中,1的个数为3,我们就让下标为1的位置存放3;2的个数为2,我们就让下标为2的位置存放2…… 

由此我们可以知道,我们申请空间的大小为数组中的最大值。

(3)思考

1.如果数组为{101,102,109,105,101,105},难道我们要申请109个空间吗?

就上述数组来说,如果我们申请了109个空间,不存在100之前的数据,那么那些空间不就浪费了吗?

因此单单按数据开辟空间是行不通的。

2.我们知道不存在为负数的下标,那如果数组中有负数怎么进行排序呢?

通过思考可能会想到将负数取绝对值,不就变成正数了吗?可是,如果负数是-5,同时数组中存在5,将负数取绝对值后,-5和5不就分不清了吗?因此这个方法也行不通。


为了不让空间浪费,并且解决负数排序的情况,我们可以开辟空间存放最小值和最大值之间的数据,即空间的大小为max - min + 1。这样,即使最小值为负数,我们让一个数减去最小的负数,结果必然是整数!


(4)代码实现

  1. void CountSort(int* arr, int n)
  2. {
  3. //找最大值和最小值
  4. int min = arr[0];
  5. int max = arr[0];
  6. for(int i = 1; i < n; i++)
  7. {
  8. if(arr[i] < min)
  9. min = arr[i];
  10. if(arr[i] > max)
  11. max = arr[i];
  12. }
  13. //确定新数组的大小
  14. int range = max - min + 1;
  15. //创建新数组
  16. int* count = (int*)malloc(sizeof(int) * n);
  17. if(count == NULL)
  18. {
  19. perror("malloc fail!");
  20. return;
  21. }
  22. //统计数组中各元素的个数
  23. for(int i = 0; i < n; i++)
  24. {
  25. count[arr[i] - min] ++;
  26. }
  27. //排序、输出
  28. int j = 0;
  29. for(int i = 0; i < range; i++)
  30. {
  31. while(count[i]--)
  32. {
  33. arr[j++] = i + min;
  34. }
  35. }
  36. }

(5)注意 

1.统计数组中的元素时,数据arr[i]对应的新数组的下标为arr[i] - min;

2.在排序、输出时,数据arr[i]对应的新数组的数据为i + min。

(6)代码解释

首先我们找到要排序的数组的最大值和最小值,创建一个新数组保存统计的数据个数的结果,最后按照新数组的数据进行排序(更新原数组)。

(7)复杂度

1.时间复杂度:O(N + range);

2.空间复杂度:O(range)。

三、排序性能对比

  1. //测试排序性能对比
  2. void TestOP()
  3. {
  4. srand(time(0));
  5. const int N = 100000;
  6. int* a1 = (int*)malloc(sizeof(int) * N);
  7. int* a2 = (int*)malloc(sizeof(int) * N);
  8. int* a3 = (int*)malloc(sizeof(int) * N);
  9. int* a4 = (int*)malloc(sizeof(int) * N);
  10. int* a5 = (int*)malloc(sizeof(int) * N);
  11. int* a6 = (int*)malloc(sizeof(int) * N);
  12. int* a7 = (int*)malloc(sizeof(int) * N);
  13. int* a8 = (int*)malloc(sizeof(int) * N);
  14. for(int i = 0; i < N; i ++)
  15. {
  16. arr1[i] = rand();
  17. arr2[i] = arr1[i];
  18. arr3[i] = arr1[i];
  19. arr4[i] = arr1[i];
  20. arr5[i] = arr1[i];
  21. arr6[i] = arr1[i];
  22. arr7[i] = arr1[i];
  23. arr8[i] = arr1[i];
  24. }
  25. //直接插入排序
  26. int begin1 = clock();
  27. InsertSort(arr1,N);
  28. int end1 = clock();
  29. //希尔排序
  30. int begin2 = clock();
  31. ShellSort(arr1,N);
  32. int end2 = clock();
  33. //直接选择排序
  34. int begin3 = clock();
  35. SelectSort(arr1,N);
  36. int end3 = clock();
  37. //堆排序
  38. int begin4 = clock();
  39. HeapSort(arr1,N);
  40. int end4 = clock();
  41. //冒泡排序
  42. int begin5 = clock();
  43. BubbleSort(arr1,N);
  44. int end5 = clock();
  45. //快速排序
  46. int begin6 = clock();
  47. QuickSort(arr1,N);
  48. int end6 = clock();
  49. //归并排序
  50. int begin7 = clock();
  51. MergeSort(arr1,N);
  52. int end7 = clock();
  53. //计数排序
  54. int begin8 = clock();
  55. CountSort(arr1,N);
  56. int end8 = clock();
  57. printf("InsertSort:%d\n",end1 - begin1);
  58. printf("ShellSort:%d\n",end2 - begin2);
  59. printf("SelectSort:%d\n",end3 - begin3);
  60. printf("HeapSort:%d\n",end4 - begin4);
  61. printf("BubbleSort:%d\n",end5 - begin5);
  62. printf("QuickSort:%d\n",end6 - begin6);
  63. printf("MergeSort:%d\n",end7 - begin7);
  64. printf("CountSort:%d\n",end8 - begin8);
  65. free(arr1);
  66. free(arr2);
  67. free(arr3);
  68. free(arr4);
  69. free(arr5);
  70. free(arr6);
  71. free(arr7);
  72. free(arr8);
  73. }

四、排序算法特性

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。 

 

稳定性验证案例

直接选择排序:58529;

希尔排序:58259;

堆排序:2222;

快速排序:53343891011。 

五、写在最后

至此我们学习了顺序表、链表、栈、队列、二叉树、排序,初阶数据结构完结~撒花!

我们C++见!!

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

闽ICP备14008679号