• 数据结构初阶之排序(四)


    今天更新完常见八大排序的最后两个排序:归并排序和计数排序

    先说简单的

    一、计数排序

    计数排序的思路非常简单

    1.先统计原数组中每个值出现的次数,存放在count数组

    2.排序,遍历count数组,对应位置的值出现几次,就往原数组中写几个这个值

    举个例子 1 2 2 1 5 4 5

    那就创建一个数组,从0-4的下标中依次对应2 3 0 1 2,可以打印出来1 1 2 2 2 4 5 5 

    里还要注意的就是count数组应当使用相对映射的方法,比如1000到1010,不能开从0到1010,而是开1000到1010,这样就可以不浪费空间

    但局限性就在于此:数据范围大的时候,就不能用计数排序,还有一点,就是当数据类型是浮点型和字符串时,计数排序同样时不适用的。

    可以看下面的动图

    计数排序

     代码如下:思路就是先找该数据列中的最大值和最小值,然后定义range为最大值和最小值的差值,之后开辟一个数组来存放次数,接下来就要统计次数,

    1. //计数排序
    2. void CountSort(int*a,int n)
    3. {
    4. int min=a[0],max=a[0];
    5. //找最大值,最小值
    6. for(int i=1;i<n;i++)
    7. {
    8. if(a[i]<min)
    9. {
    10. min=[i];
    11. }
    12. if(a[i]>max)
    13. {
    14. max=a[i];
    15. }
    16. }
    17. int range = max-min+1;//区间范围个数,用于开辟count数组
    18. int* count = (int*)calloc(range,sizeof(int));//开辟一个数组用来存放次数
    19. //统计次数
    20. for(int i=0;i<n;i++)
    21. {
    22. count[a[i]-min]++;//对相对位置进行计数
    23. }
    24. //遍历计数数组,对原数组进行排序
    25. int i=0;
    26. for(int j=0;j<range;j++)
    27. {
    28. while(count[j]--)
    29. {
    30. a[i++]=j+min;
    31. }
    32. }
    33. }

    二、归并排序

    归并排序:是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行

    比如下面这组数据,

    归并排序

     代码如下,这里需要开辟一个大小为N的数组来存放归并排序分出来的数,不断分不断分,分到最小元素时,开始并,一直并到有序。

    1. void _MergeSort(int*a,int left,int right,int*temp)
    2. {
    3. if(left>=right)
    4. return;//当区间不存在或者只有一个值时,返回
    5. int mid = (right+left)/2;//mid用来分组,分成[left,mid],[mid+1,right]
    6. //[left,mid],[mid+1,right]
    7. _MergeSort(a,left,mid,temp);//递归左边
    8. _MergeSort(a,mid+1,right,temp);//递归右边
    9. //归并
    10. int begin1 = left,end1 = mid;//避免混淆,这里用begin1,end1,begin2,end2来保存 区间
    11. int begin2 = mid+1,end2 = right;
    12. int index = left;
    13. while(begin1<=end1 && begin2<=end2)//两组中的数据有一个结束就结束
    14. {
    15. if(a[begin1]<a[begin2])
    16. {
    17. temp[index++]=a[begin1++];//将数据拷进去,并++指向下一个位置
    18. }
    19. else
    20. {
    21. temp[index++]=a[begin2++];//将数据拷进去,并++指向下一个位置
    22. }
    23. }
    24. //出来后有一组数据还没有拷贝进去,如果左边的没结束,则将左边剩下的拷贝进去,右边没结束,则将右边剩下的拷贝进去
    25. while(begin1<=end1)
    26. {
    27. temp[index++]=a[begin1++];//将剩下的数据全部拷贝进去
    28. }
    29. while(begin2<=end2)
    30. {
    31. temp[index++]=a[begin2++];//将剩下的数据全部拷贝进去
    32. }
    33. for(int i=left,i<=right;i++)
    34. {
    35. a[i]=temp[i];//最后还需要将temp中的数据拷贝到原数组
    36. }
    37. }
    38. void MergeSort(int* a,int n)
    39. {
    40. int *temp =(int*)malloc(sizeof(int)*n);//开辟一个临时数组来存放归并后的数据
    41. _MergeSort(a,0,n-1,temp);
    42. free(temp);
    43. }

    时间复杂度:O(n*logn)
    空间复杂度:O(n)

    归并排序的非递归思想不难,这个实现起来还是比较困难的,因为细节比较多

    这里的细节在于边界修正,第一种end1越界。第二种begin2越界。第三种end2越界。

    这里有两种写法,第一种是全部的边界修正,第二种是只修正end2,也就是第一种和第二种情况不修正,因为可以直接将第一种和第二种剩下的数字不归并,直接

    1. void MergeSortNonR(int* a, int n)
    2. {
    3. int* temp = (int*)malloc(sizeof(int) * n);//开辟一个临时数组
    4. if (temp == NULL)
    5. {
    6. return;
    7. }
    8. int groupNum = 1;
    9. while (groupNum < n)
    10. {
    11. for (int i = 0; i < n; i += 2 * groupNum)
    12. {
    13. //[begin1][end1] [begin2,end2]
    14. //控制组进行归并
    15. int begin1 = i, end1 = i + groupNum - 1;
    16. int begin2 = i + groupNum, end2 = i + groupNum * 2 - 1;
    17. int index = begin1;
    18. //数组的数据个数,并不一定是按整数倍
    19. //1、[begin2,end2]不存在,修正为一个不存在的区间
    20. //让它进不去归并
    21. if (begin2 >= n)//
    22. {
    23. begin2 = n + 1;
    24. end2 = n;
    25. }
    26. if (end1 >= n)//end1越界,修正一下
    27. {
    28. end1 = n - 1;
    29. }
    30. if (end2 >= n)//end2越界,需要修正后归并
    31. {
    32. end2 = n - 1;
    33. }
    34. while (begin1 <= end1 && begin2 <= end2)//两组中的数据有一个结束就结束
    35. {
    36. if (a[begin1] < a[begin2])
    37. {
    38. temp[index++] = a[begin1++];//将数据拷进去,并++指向下一个位置
    39. }
    40. else
    41. {
    42. temp[index++] = a[begin2++];//将数据拷进去,并++指向下一个位置
    43. }
    44. }
    45. //出来后有一组数据还没有拷贝进去,如果左边的没结束,则将左边剩下的拷贝进去,右边没结束,则将右边剩下的拷贝进去
    46. while (begin1 <= end1)
    47. {
    48. temp[index++] = a[begin1++];//将剩下的数据全部拷贝进去
    49. }
    50. while (begin2 <= end2)
    51. {
    52. temp[index++] = a[begin2++];//将剩下的数据全部拷贝进去
    53. }
    54. }
    55. //拷贝回原数组
    56. for (int i = 0; i < n; i++)
    57. {
    58. a[i] = temp[i];//最后还需要将temp中的数据拷贝到原数组
    59. }
    60. groupNum *= 2;
    61. }
    62. free(temp);
    63. }

    第二种方法: 

    1. void MergeSortNonR(int* a, int n)
    2. {
    3. int* tmp = (int*)malloc(sizeof(int)*n);
    4. if (tmp == NULL)
    5. {
    6. printf("malloc fail\n");
    7. exit(-1);
    8. }
    9. // 休息1148继续
    10. int gap = 1;
    11. while (gap < n)
    12. {
    13. //printf("gap=%d->", gap);
    14. for (int i = 0; i < n; i += 2 * gap)
    15. {
    16. // [i,i+gap-1][i+gap, i+2*gap-1]
    17. int begin1 = i, end1 = i + gap - 1;
    18. int begin2 = i + gap, end2 = i + 2 * gap - 1;
    19. // end1越界或者begin2越界,则可以不归并了
    20. if (end1 >= n || begin2 >= n)
    21. {
    22. break;
    23. }
    24. else if (end2 >= n)
    25. {
    26. end2 = n - 1;
    27. }
    28. //printf("[%d,%d] [%d, %d]--", begin1, end1, begin2, end2);
    29. int m = end2 - begin1 + 1;//这里开辟数组的大小发生了变化
    30. int j = begin1;
    31. while (begin1 <= end1 && begin2 <= end2)
    32. {
    33. if (a[begin1] < a[begin2])
    34. {
    35. tmp[j++] = a[begin1++];
    36. }
    37. else
    38. {
    39. tmp[j++] = a[begin2++];
    40. }
    41. }
    42. while (begin1 <= end1)
    43. {
    44. tmp[j++] = a[begin1++];
    45. }
    46. while (begin2 <= end2)
    47. {
    48. tmp[j++] = a[begin2++];
    49. }
    50. memcpy(a + i, tmp + i, sizeof(int)* m);
    51. }
    52. gap *= 2;
    53. }
    54. free(tmp);
    55. }

  • 相关阅读:
    linux 输出重定向
    字节跳动八进八出,offer到手,发现项目不重要算法才最重要
    Vue课程48-学习小结
    R语言进行数据分组聚合统计变换(Aggregating transforms)、计算dataframe数据的分组最小值(min)
    豆瓣图书评分数据的可视化分析
    数组12— map() :转换数组元素
    sql窗口函数学习笔记
    Opencv cuda版本编译
    圆角矩形 渐变边框 css
    视频怎么压缩?视频太大这样处理变小
  • 原文地址:https://blog.csdn.net/yzy521wjm/article/details/125584800