• 归并排序与计数排序(含代码)


    目录

    目录:

            1:归并排序递归

            2:归并排序的非递归

            3:计数排序的思想


    1:归并排序递归

                  思路:归并排序是采用分治算法的一种排序,将两个有序的子数组合并到一个数组中去使得数组完全有序,所以我们先使子数组有序,在使整体的数组有序,这种先让子数组有序,最后在使整体完全有序,也称为二路归并。

            归并排序的核心步骤如下图:

             

                    

            思路:先将数组划分为两个区间,使得左右两个区间有序 ,在借用tmp数组将两个有序的区间合并成一个有序的区间最后在将tmp数组中的值拷贝到原数组中去,而要使左右区间有序,那么我们又得将左右区间划分为两个部分,使得左右区间的子区间变成有序,在利用tmp数组来进行归并.......,这个过程肯定涉及递归与合并的过程,最后归并的过程是左右子区间都有序,即区间数为1的时候,就得开始归并了。这个思路需要自行去画图来感悟,才能有更好的理解。

            如下图:

    代码如下:

    1. void _MergeSort(int* a, int* tmp, int begin, int end)
    2. {
    3. //递归返回条件
    4. if (begin >= end)
    5. return;
    6. //将区间分为左右两个子区间
    7. int mid = (begin + end) / 2;
    8. //递归左边
    9. _MergeSort(a, tmp, begin, mid);
    10. _MergeSort(a, tmp, mid + 1, end);
    11. //走到这里,说明我们得开始合并两个子数组了
    12. int begin1 = begin, end1 = mid;
    13. int begin2 = mid + 1, end2 = end;
    14. int index = begin;//要保证index在tmp数组中同一个位置
    15. while (begin1 <= end1 && begin2 <= end2)
    16. {
    17. //取小的尾插
    18. if (a[begin1] > a[begin2])
    19. {
    20. tmp[index++] = a[begin2++];
    21. }
    22. else
    23. {
    24. tmp[index++] = a[begin1++];
    25. }
    26. }
    27. //肯定有一个区间没有完全插入到tmp数组
    28. while (begin1 <= end1)
    29. {
    30. tmp[index++] = a[begin1++];
    31. }
    32. while (begin2 <= end2)
    33. {
    34. tmp[index++] = a[begin2++];
    35. }
    36. //拷贝回原数组 区间为左闭右闭所以还得加1,拷贝空间的个数
    37. memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
    38. }
    39. void MergeSort(int* a, int n)
    40. {
    41. int* tmp = (int*)malloc(sizeof(int) * n);
    42. _MergeSort(a, tmp, 0, n - 1);
    43. }

            归并排序的时间复杂度为标准的O(N*logN),因为每一层都有n个数需要合并,一共有高度层。

            空间复杂度:O(N),开了一块临时的空间,在该空间上进行插入。

    2:归并排序的非递归

            我们先上代码吧!!

            

    1. void MergeSortNonR(int* a, int n)
    2. {
    3. int* tmp = (int*)malloc(sizeof(int) * n);
    4. if (tmp == NULL)
    5. {
    6. perror("malloc fail:");
    7. return;
    8. }
    9. //归并的数据个数
    10. int gap = 1;
    11. //数组中每一组都得归并
    12. while (gap < n)
    13. {
    14. for (int i = 0; i < n; i += 2 * gap)
    15. {
    16. int begin1 = i, end1 = i + gap - 1;
    17. int begin2 = i + gap, end2 = i + 2 * gap - 1;
    18. //[begin1,end1]与[begin2,end2]一起进行归并
    19. int index = i;
    20. if (end1 >= n || begin2 >= n)
    21. {
    22. break;
    23. }
    24. if (end2 >= n)
    25. {
    26. end2 = n - 1;
    27. }
    28. while (begin1 <= end1 && begin2 <= end2)
    29. {
    30. if (a[begin1] >= a[begin2])
    31. {
    32. tmp[index++] = a[begin2++];
    33. }
    34. else
    35. {
    36. tmp[index++] = a[begin1++];
    37. }
    38. }
    39. //有一个未尾插完
    40. while (begin1 <= end1)
    41. {
    42. tmp[index++] = a[begin1++];
    43. }
    44. while (begin2 <= end2)
    45. {
    46. tmp[index++] = a[begin2++];
    47. }
    48. memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
    49. }
    50. gap *= 2;
    51. }
    52. }

            归并排序递归的思路:要想整体有序就得左右子区间先有序,一次递归下去

            非递归的思路:直接先从11开始归并,然后22归并,44归并,一直以两倍进行归并下去

           在上图采用非递归进行归并的时候,我们需要注意的就是4这个数字,当我们将数组划分为

            [begin1,end1],[begin2,end2]的时候,假如我们的end1越界了,即end1>=n或begin2越界,我们就不需要对他进行归并,直接break跳出循环就行了。

            当只有end2越界的时候,这时需要先将end2的值改为n-1,然后在进行归并即可。

            非递归形式下的时间与空间复杂度与递归一样。

    3.计数排序

            关于计数排序应该是在这些排序中思路最简单的排序了。

            计数排序的思想是,统计原数组中值的个数,在另外一块空间中对应下标++,然后从下标为0开始遍历数组,当遍历到数组中的值不为0时,将该数尾插到原数组。

            我们通过图来讲解

            这种方式是采用了绝对映射的方式,即原数组中值出现,对应在count数组下标处进行++

            而当数据非常大,但是也属于相对集中的范围,则我们需要采取相对映射的方式来进行。

            相对映射是可以用来减少开辟空间的个数的。

            图:

            从这里我们也能看出来,计数排序的缺点:1.只能对相对集中的值进行排序,否则会浪费空间

            代码如下:

    1. void CountSort(int* a, int n)
    2. {
    3. int max = a[0];
    4. int min = a[0];
    5. //为相对映射做准备,统计数组中最大与最小值
    6. for (int i = 0; i < n; i++)
    7. {
    8. if (a[i] > max)
    9. {
    10. max = a[i];
    11. }
    12. if (a[i] < min)
    13. {
    14. min = a[i];
    15. }
    16. }
    17. //开辟range个空间,用来统计每个数出现的次数
    18. int range = max - min + 1;
    19. //在统计前一定要将count数组中值全部变为0,calloc默认初始化可以实现这一功能
    20. int* count = (int*)calloc(range, sizeof(int));
    21. //统计数组中元素的个数
    22. for (int i = 0; i < n; i++)
    23. { //相对映射在a[i]-min处++
    24. count[a[i] - min]++;
    25. }
    26. int index = 0;
    27. for (int i = 0; i <= range; i++)
    28. {
    29. //进行最后的排序,并且将值回显
    30. while (count[i] > 0)//统计k次
    31. {
    32. a[index++] = i + min;
    33. count[i]--;
    34. }
    35. }
    36. }

            时间复杂度:为O(N+range),N为数组的个数,range为映射开辟的空间

            空间复杂度:O(range)

            

            本章到这里在知识点就已经分享完毕了,感谢大家的耐心观看,如果你觉得有用的话可以给博主一个赞哦!!!

  • 相关阅读:
    设计模式 之单例模式
    SQL模板-用户留存率计算
    企业如何使用CRM客户管理系统全面了解客户
    【大模型应用开发教程】02_LangChain介绍
    黑马瑞吉外卖之套餐信息的分页查询
    Flutter “Hello world“ 程代码
    MySQL数据库(基础)
    第二章 计算机算术
    MyBatis的各种查询功能
    关于#c++#的问题:在while(true)添加cout<<"按学号删除学生记录"<<endl
  • 原文地址:https://blog.csdn.net/2201_75964502/article/details/134012065