排序桶排序

Posted 想转IT的机械君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序桶排序相关的知识,希望对你有一定的参考价值。

1.原理

“桶排序”的原理很好理解,举一个学习委员或者科代表收作业的例子。收作业这项工作很简单,我们可以逐步考虑收作业场景的“升级”。第一阶段,老师只要求你把同学们的作业交上来。这时候,同学们把作业交上来然后你抱给老师,任务完成;第二阶段,老师要了解下同学们作业交的情况,需要统计交了多少人,没交多少人。这时候,同学们作业交上来以后你得数一遍,然后交上去,任务完成;第三阶段,老师不光要知道交的人数,还需要知道没交作业同学的具体名单。不仅如此,老师还提出了新的要求,他要给每个同学的作业打平时成绩,要求打完成绩之后能非常方便的“登分”。

这时候收过作业的同学都知道,一种比较简单的处理方式就是让同学们在作业本上标上学号,作业本收上来之后按学号大小顺序进行排序,学号没出现的就是没交作业的,老师按学号顺序“登分”也非常容易,任务完成,需求解决。问题就在于怎么能较快地完成排序操作?

那么借助“桶排序”的思想,我们的操作可能是这样的:假设班上共有80名同学,学号简化后按1-80进行编号。首先把作业本遍历一遍,按0-9,10-19,20-29,...,80-89的区间将作业本分成10摞。在每个同学都交作业的情况下,一摞中最多10本作业,将每摞作业本按学号从小到大排序。最后将10摞作业本按小号在上大号在下的顺序合并即可得到“有序的作业本”。(科代表们在收作业的时候可以边分摞边用“插入排序”的思想进行每摞中的排序节省时间-_-)

回顾这个例子中收作业并排序的过程,可以将“桶排序”的思路简单进行归纳整理:1.获取待排序数组的最大、最小值,明确数据范围;2.适当选择“桶”容量,即按多大区间进行数据切分;3.遍历数据,将每个数组装入对应的“桶”中;4.对切分后的每个“桶”中的数据进行排序;5.按序将每个“桶”中的数据取出拼接为整体,得到排序后的结果。排序过程如下图所示:

2.复杂度

在数据分布较为均匀,即最终每个桶中的数据量差异不大的情况下,桶排序的时间复杂度接近O(n),简要分析如下:

如果要排序的数据有 n 个,均匀地划分到 m 个桶内,每个桶分 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度为 O(m * k * logk),而 k=n/m,所以整个桶排序的时间复杂度为 O(n*log(n/m))。当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。

但是如果数据分布不均匀,大量数据集中在一个很小的区间段内时,大量数据会被装进同一个桶内,这时算法的性能就会退化为复杂度为 O(nlogn) 的算法了。
由于桶排序算法执行过程中需要使用“桶”对全部数据进行临时储存,桶内排序时通过对快速排序进行巧妙设计可以实现原地排序,那么桶排序的空间复杂度也为O(n)。

3.代码实现

我们将“桶排序”过程用代码进行实现,其中采用快速排序对每个“桶”中的数据进行排序操作,完整代码如下:

 
   
   
 
  1. /**

  2. * 桶排序

  3. * Created by mootong on 2020.1.13

  4. */

  5. public class BucketSort {

  6. /**

  7. * 桶排序

  8. *

  9. * @param target 目标数组

  10. * @param bucketSize 桶容量

  11. */

  12. public static void bucketSort(int[] target, int bucketSize) {

  13. if (target.length < 2) return;

  14. //扫描数组获取最大最小值

  15. int maxValue = target[0];

  16. int minValue = target[0];

  17. for (int i = 0; i < target.length; i++) {

  18. if (target[i] < minValue) {

  19. minValue = target[i];

  20. }

  21. if (target[i] > maxValue) {

  22. maxValue = target[i];

  23. }

  24. }

  25. int bucketCount = (maxValue - minValue) / bucketSize + 1;

  26. //使用二维数组实现桶

  27. int[][] bucket = new int[bucketCount][bucketSize];

  28. //用以记录每个桶中元素的数量

  29. int[] bucketIndex = new int[bucketCount];

  30. //数据入桶

  31. for (int i = 0; i < target.length; i++) {

  32. int index = (target[i] - minValue) / bucketSize;

  33. if (bucketIndex[index] == bucket[index].length) {

  34. ensureCapaciity(bucket , index);

  35. }

  36. //可将下面代码合并为一行

  37. //bucket[index][bucketIndex[index]++] = target[i];

  38. bucket[index][bucketIndex[index]] = target[i];

  39. bucketIndex[index]++;

  40. //数据入桶结束

  41. }

  42. //逐一对每个桶进行排序,每个桶排序结束后将桶中数据顺序储存在同一文件中

  43. //记录已排序数据的个数

  44. int k = 0;

  45. for (int i = 0; i < bucket.length; i++) {

  46. //如果桶为空

  47. if (bucketIndex[i] == 0){

  48. continue;

  49. }

  50. //易错点,此处参数不能写为:

  51. //quickSort(bucket[i],0,bucket[i].length-1);

  52. //因为数组扩容以后元素个数与数组容量不一定相等

  53. quickSort(bucket[i],0,bucketIndex[i]-1);

  54. //bucketIndex[i]与前述同理,需要的是实际元素的个数

  55. for (int j = 0; j < bucketIndex[i]; j++) {

  56. target[k++] = bucket[i][j];

  57. }

  58. }

  59. }

  60. /**

  61. * 快速排序

  62. *

  63. * @param arr

  64. * @param begin

  65. * @param end

  66. */

  67. public static void quickSort(int[] arr, int begin, int end) {

  68. if (begin >= end) return;

  69. int partitionIndex = partition(arr, begin, end);

  70. quickSort(arr, begin, partitionIndex - 1);

  71. quickSort(arr, partition + 1, end);

  72. }


  73. /**

  74. * 快排分区实现

  75. *

  76. * @param arr

  77. * @param begin

  78. * @param end

  79. */

  80. public static int partition(int[] arr, int begin, int end) {

  81. int j = begin;

  82. int comparedValue = arr[end];

  83. for (int i = begin; i < end; i++) {

  84. if (arr[i] <= comparedValue) {

  85. swap(arr, i, j);

  86. j++;

  87. }

  88. }

  89. swap(arr, j, end);

  90. return j;

  91. }


  92. /**

  93. * 交换数组两元素

  94. *

  95. * @param arr

  96. * @param i

  97. * @param j

  98. */

  99. public static void swap(int[] arr, int i, int j) {

  100. int temp = arr[i];

  101. arr[i] = arr[j];

  102. arr[j] = temp;

  103. }


  104. /**

  105. * 数组扩容

  106. *

  107. * @param arr 待扩容数组

  108. */

  109. public static void ensureCapaciity(int[][] arr , int index) {

  110. int[] tempArr = arr[index];

  111. int[] newArr = new int[tempArr.length*2];

  112. for (int i = 0; i < tempArr.length; i++) {

  113. newArr[i] = tempArr[i];

  114. }

  115. arr[index] = newArr;

  116. }

  117. }

4.代码实现易错点

在桶排序算法的实现过程中,易错点有:1.桶数量的计算方式应该为:(maxValue - minValue) / bucketSize + 1,注意后面的+1;2.对每个桶内的数据进行排序的时候,传入排序函数的数组结尾下标应该为“桶”中实际数据个数-1,不能传入“桶”容量,因为“桶”会有未装满的情况。


以上是关于排序桶排序的主要内容,如果未能解决你的问题,请参考以下文章

线性排序:桶排序计数排序

线性排序:桶排序计数排序

桶排序和计数排序

排序算法——桶排序

桶排序代码

图解排序 8/10 - 桶排序