• 八大排序(三)堆排序,计数排序,归并排序


    一、堆排序

    什么是堆排序:堆排序(Heap Sort)就是对直接选择排序的一种改进。此话怎讲呢?直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的,但是在选出最大或者最小的数后,并没有对原来的序列进行改变,这使得下一次选数时还需要对全部数据进行比较,效率大大降低。

    堆排序的原理:

    1. 将待排序序列构造成一个大顶堆
    2. 此时,整个序列的最大值就是堆顶的根节点
    3. 将其与末尾元素进行交换,此时末尾就为最大值。
    4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

    如果读到这里,对堆的一些概念不懂得可以翻阅我的另一篇博客“数据结构——【堆】_#欲速则不达#的博客-CSDN博客”,这里面有更详细的堆概念介绍。

    代码实现:

    1. #include<stdio.h>
    2. //交换函数
    3. void Swap(int* p1, int* p2)
    4. {
    5. int temp = *p1;
    6. *p1 = *p2;
    7. *p2 = temp;
    8. }
    9. //向下调整
    10. void down(int*a,int n,int parent)
    11. {
    12. //大堆
    13. int child = parent * 2 + 1;
    14. //大堆
    15. while (child <n)
    16. {
    17. if (a[child] < a[child + 1] && n > child + 1)
    18. {
    19. child++;
    20. }
    21. if (a[child] > a[parent])
    22. {
    23. Swap(&a[child], &a[parent]);
    24. parent = child;
    25. child = parent * 2 + 1;
    26. }
    27. else
    28. {
    29. break;
    30. }
    31. }
    32. }
    33. int main()
    34. {
    35. //排序该数组
    36. int arr[] = { 5,9,2,6,0,4,8,1,3,7 };
    37. //建大堆
    38. for (int i = 4; i >=0; i--)
    39. {
    40. down(arr, 10, i);
    41. }
    42. //将数组再次插入其中
    43. int end = 10 - 1;
    44. while (end > 0)
    45. {
    46. Swap(&arr[0], &arr[end]);
    47. down(arr, end, 0);
    48. end--;
    49. }
    50. //打印
    51. for (int i = 0; i < 10; i++)
    52. {
    53. printf("%d ", arr[i]);
    54. }
    55. return 0;
    56. }

    时间复杂度:

    二、计数排序

    计数排序思路:

    1. 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
    2. 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
    3. 对额外空间内数据进行计算,得出每一个元素的正确位置;
    4. 将待排序集合每一个元素移动到计算得出的正确位置上。

    代码实现:

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. void CountSort(int* a, int n)
    5. {
    6. int min = a[0];
    7. int max = a[0];
    8. for (int i = 0; i < n; i++)
    9. {
    10. if (a[i] > max)
    11. {
    12. max = a[i];
    13. }
    14. if (a[i] < min)
    15. {
    16. min = a[i];
    17. }
    18. }
    19. int range = max - min + 1;
    20. int* count = (int*)malloc(sizeof(int) * range);
    21. printf("range=%d \n", range);
    22. if (count == NULL)
    23. {
    24. perror("malloc fail");
    25. exit(-1);
    26. }
    27. memset(count, 0,sizeof(int) * range);
    28. for (int i = 0; i < n; i++)
    29. {
    30. count[a[i] - min]++;
    31. }
    32. //排序
    33. int j = 0;
    34. for (int i = 0; i < range; i++)
    35. {
    36. while (count[i]--)
    37. {
    38. a[j++] = i + min;
    39. }
    40. }
    41. }
    42. int main()
    43. {
    44. int arr[] = { 4,8,4,3,2,0,9,1,6,8,3,21,7,4,2,1,6,8 };
    45. int len = sizeof(arr) / sizeof(int);
    46. CountSort(arr, len);
    47. for (int i = 0; i < len; i++)
    48. {
    49. printf("%d ",arr[i] );
    50. }
    51. return 0;
    52. }

    时间复杂度:

    平均时间复杂度:Ο(n+k)

    空间复杂度:Ο(n+k)

    三、归并排序

    归并排序思路:

    归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。

    将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
    将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

    代码实现: 

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. void _MergeSort(int* a, int* tmp, int begin, int end)
    5. {
    6. if (begin >= end)
    7. {
    8. return;
    9. }
    10. int mid = (begin + end) / 2;
    11. //分为左右两块
    12. //
    13. _MergeSort(a, tmp, begin, mid);
    14. //
    15. _MergeSort(a, tmp, mid+1, end);
    16. int begin1 = begin;
    17. int begin2 = mid+1;
    18. int end1 = mid;
    19. int end2 = end;
    20. int index = begin;
    21. while (begin1 <= end1 && begin2 <= end2)
    22. {
    23. if (a[begin1] < a[begin2])
    24. {
    25. tmp[index++] = a[begin1++];
    26. }
    27. else
    28. {
    29. tmp[index++] = a[begin2++];
    30. }
    31. }
    32. while (begin1 <= end1)
    33. {
    34. tmp[index++] = a[begin1++];
    35. }
    36. while (begin2 <= end2)
    37. {
    38. tmp[index++] = a[begin2++];
    39. }
    40. memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
    41. }
    42. void MergeSort(int* a, int n)
    43. {
    44. int* tmp = (int*)malloc(sizeof(int) * n);
    45. if (tmp == NULL)
    46. {
    47. perror("malloc fail");
    48. return;
    49. }
    50. _MergeSort(a, tmp, 0, n - 1);
    51. free(tmp);
    52. }
    53. /// ///
    54. //归并排序非递归
    55. void MergeSortNonR(int* a, int n)
    56. {
    57. int* tmp = (int*)malloc(sizeof(int) * n);
    58. if (tmp == NULL)
    59. {
    60. perror("malloc fail");
    61. return;
    62. }
    63. int gap = 1;
    64. while (gap < n)
    65. {
    66. for (int i = 0; i < n; i += 2 * gap)
    67. {
    68. int begin1 = i, end1 = i + gap - 1;
    69. int begin2 = i + gap, end2 = i + 2 * gap - 1;
    70. // [begin1,end1] [begin2,end2] 归并
    71. if (begin2 >= n)
    72. {
    73. break;
    74. }
    75. // 如果第二组的右边界越界,修正一下
    76. if (end2 >= n)
    77. {
    78. end2 = n - 1;
    79. }
    80. int index = i;
    81. while (begin1 <= end1 && begin2 <= end2)
    82. {
    83. if (a[begin1] < a[begin2])
    84. {
    85. tmp[index++] = a[begin1++];
    86. }
    87. else
    88. {
    89. tmp[index++] = a[begin2++];
    90. }
    91. }
    92. while (begin1 <= end1)
    93. {
    94. tmp[index++] = a[begin1++];
    95. }
    96. while (begin2 <= end2)
    97. {
    98. tmp[index++] = a[begin2++];
    99. }
    100. // 拷贝回原数组
    101. memcpy(a + i, tmp + i, (end2-i+1) * sizeof(int));
    102. }
    103. gap *= 2;
    104. }
    105. free(tmp);
    106. }
    107. void Print(int* arr, int n)
    108. {
    109. for (int i = 0; i < n; i++)
    110. {
    111. printf("%d ", arr[i]);
    112. }
    113. printf("\n");
    114. }
    115. int main()
    116. {
    117. int arr[] = { 2,5,7,9,1,0,3,4,6,8,10};
    118. int len = sizeof(arr) / sizeof(int);
    119. MergeSortNonR(arr, len);
    120. Print(arr, len);
    121. return 0;
    122. }

    时间复杂度:

    归并排序方法就是把一组n个数的序列,折半分为两个序列,然后再将这两个序列再分,一直分下去,直到分为n个长度为1的序列。然后两两按大小归并。如此反复,直到最后形成包含n个数的一个数组。

    归并排序总时间 = 分解时间 + 子序列排好序时间 + 合并时间

    无论每个序列有多少数都是折中分解,所以分解时间是个常数,可以忽略不计,则:

    归并排序总时间 = 子序列排好序时间 + 合并时间

    假设处理的数据规模大小为 n,运行时间设为:T(n),则T(n) = n,当 n = 1时,T(1) = 1

    由于在合并时,两个子序列已经排好序,所以在合并的时候只需要 if 判断即可,所以n个数比较,合并的时间复杂度为 n。

    将 n 个数的序列,分为两个 n/2 的序列,则:T(n) = 2T(n/2) + n
    将 n/2 个数的序列,分为四个 n/4 的序列,则:T(n) = 4T(n/4) + 2n
    将 n/4 个数的序列,分为八个 n/8 的序列,则:T(n) = 8T(n/8) + 3n

    将 n/2k 个数的序列,分为2k个 n/2k 的序列,则:T(n) = 2kT(n/2k) + kn
    当 T(n/2k) = T(1)时, 即n/2k = 1(此时也是把n分解到只有1个数据的时候),转换为以2为底n的对数:k = log2n,把k带入到T(n)中,得:T(n) = n + nlog2n。

    使用大O表示法,去掉常数项 n,省略底数 2,则归并排序的时间复杂度为:O(nlogn)

    四、八大排序的复杂度和稳定性

    稳定排序有:插入排序、冒泡排序、归并排序
    不稳定排序:希尔排序、快速排序、选择排序、堆排序
    口诀,不稳定的排序:快(快排)些(希尔)选(选择)一堆(堆排)

  • 相关阅读:
    成为数据分析师要具备什么能力——功法篇(上)
    深度学习与目标检测:从卷积神经网络到YOLOv8概念介绍
    力扣:1592. 重新排列单词间的空格
    机器学习实战系列[一]:工业蒸汽量预测(最新版本上篇)含数据探索特征工程等
    Django测试环境搭建及ORM查询(创建外键|跨表查询|双下划线查询 )
    【面试心得】WebBench 整理
    基于ffmpeg给视频添加时间字幕
    C# Winform编程(1)基础篇
    《C++ Primer》第2章 变量(二)
    [管理与领导-93]:IT基层管理者 - 扩展技能 - 5 - 职场丛林法则 -7- 复杂问题分析能力与复杂问题的解决能力:系统化思维
  • 原文地址:https://blog.csdn.net/m0_69323023/article/details/133500098