排序桶排序
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.代码实现
我们将“桶排序”过程用代码进行实现,其中采用快速排序对每个“桶”中的数据进行排序操作,完整代码如下:
/**
* 桶排序
* Created by mootong on 2020.1.13
*/
public class BucketSort {
/**
* 桶排序
*
* @param target 目标数组
* @param bucketSize 桶容量
*/
public static void bucketSort(int[] target, int bucketSize) {
if (target.length < 2) return;
//扫描数组获取最大最小值
int maxValue = target[0];
int minValue = target[0];
for (int i = 0; i < target.length; i++) {
if (target[i] < minValue) {
minValue = target[i];
}
if (target[i] > maxValue) {
maxValue = target[i];
}
}
int bucketCount = (maxValue - minValue) / bucketSize + 1;
//使用二维数组实现桶
int[][] bucket = new int[bucketCount][bucketSize];
//用以记录每个桶中元素的数量
int[] bucketIndex = new int[bucketCount];
//数据入桶
for (int i = 0; i < target.length; i++) {
int index = (target[i] - minValue) / bucketSize;
if (bucketIndex[index] == bucket[index].length) {
ensureCapaciity(bucket , index);
}
//可将下面代码合并为一行
//bucket[index][bucketIndex[index]++] = target[i];
bucket[index][bucketIndex[index]] = target[i];
bucketIndex[index]++;
//数据入桶结束
}
//逐一对每个桶进行排序,每个桶排序结束后将桶中数据顺序储存在同一文件中
//记录已排序数据的个数
int k = 0;
for (int i = 0; i < bucket.length; i++) {
//如果桶为空
if (bucketIndex[i] == 0){
continue;
}
//易错点,此处参数不能写为:
//quickSort(bucket[i],0,bucket[i].length-1);
//因为数组扩容以后元素个数与数组容量不一定相等
quickSort(bucket[i],0,bucketIndex[i]-1);
//bucketIndex[i]与前述同理,需要的是实际元素的个数
for (int j = 0; j < bucketIndex[i]; j++) {
target[k++] = bucket[i][j];
}
}
}
/**
* 快速排序
*
* @param arr
* @param begin
* @param end
*/
public static void quickSort(int[] arr, int begin, int end) {
if (begin >= end) return;
int partitionIndex = partition(arr, begin, end);
quickSort(arr, begin, partitionIndex - 1);
quickSort(arr, partition + 1, end);
}
/**
* 快排分区实现
*
* @param arr
* @param begin
* @param end
*/
public static int partition(int[] arr, int begin, int end) {
int j = begin;
int comparedValue = arr[end];
for (int i = begin; i < end; i++) {
if (arr[i] <= comparedValue) {
swap(arr, i, j);
j++;
}
}
swap(arr, j, end);
return j;
}
/**
* 交换数组两元素
*
* @param arr
* @param i
* @param j
*/
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 数组扩容
*
* @param arr 待扩容数组
*/
public static void ensureCapaciity(int[][] arr , int index) {
int[] tempArr = arr[index];
int[] newArr = new int[tempArr.length*2];
for (int i = 0; i < tempArr.length; i++) {
newArr[i] = tempArr[i];
}
arr[index] = newArr;
}
}
4.代码实现易错点
在桶排序算法的实现过程中,易错点有:1.桶数量的计算方式应该为:(maxValue - minValue) / bucketSize + 1,注意后面的+1;2.对每个桶内的数据进行排序的时候,传入排序函数的数组结尾下标应该为“桶”中实际数据个数-1,不能传入“桶”容量,因为“桶”会有未装满的情况。
以上是关于排序桶排序的主要内容,如果未能解决你的问题,请参考以下文章