计数排序和桶排序
目录
1、计数排序
计数排序是一种非比较性质的排序算法,元素从未排序状态变为已排序状态的过程,是由额外空间的辅助和元素本身的值决定的。计数排序过程中不存在元素之间的比较和交换操作,根据元素本身的值,将每个元素出现的次数记录到辅助空间后,通过对辅助空间内数据的计算,即可确定每一个元素最终的位置。
1.1 算法描述
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
例如:
待排序集合:[3, -1, 2, 3, 1]
(1)序列中最大值为:3,最小值为-1:,根据序列中最大值和最小值的差值范围5,可得申请额外空间大小为5
(2)按要求放入数据
(3)遍历数组,回填。
1.2 动图演示
2.3 算法性能分析
算法 | 最好时间 | 最坏时间 | 平均时间 | 额外空间 | 稳定性 |
---|---|---|---|---|---|
计数排序 | O(n + k) | O(n + k) | O(n + k) | k | 稳定 |
2.4 算法实现
/**
* 计数排序
*/
public class CountSort {
public static void sort(int[] nums){
if(nums == null || nums.length == 0)
return;
int mix = nums[0];
int max = nums[0];
for(int i = 0; i < nums.length; ++i){ //查找数组最大和最小值
if(nums[i] < mix)
mix = nums[i];
if(nums[i] > max)
max = nums[i];
}
int[] tmep = new int[max - mix + 1]; //创建辅助数组
for(int i = 0; i < nums.length; ++i){
tmep[nums[i] - mix]++;
}
int index = 0;
int flag = 0;
while(index < nums.length){ //从辅助数组中按顺序读取
if(tmep[flag] != 0){
nums[index++] = flag + mix;
tmep[flag]--;
}else{
flag++;
}
}
}
public static void main(String args[]){
int[] nums = new int[]{-1,-5,6,9,4,-2,12,11,-6};
CountSort.sort(nums);
for(int num : nums){
System.out.print(num + " ");
}
}
}
运行结果:
-6 -5 -2 -1 4 6 9 11 12
2.5 算法的优化
优化思路:利用数据挖掘对序列进行简单的数据归约,根据归约后映射的值把序列分治为比较均匀的序列,再使用计数排序对子序列进行排序,从而在保证时间效率的情况下减少了空间的分配。
例如:输入{10, 32, 14, 34, 56, 10000028, 10000034, 10000037, 10000089, 10000023},最小值是10,最大值是10000089,数组的长度是10,最大值、最小值的差为10000079
可以把序列划分为两个子序列{10,32,14,34,56},{10000028, 10000034, 10000037, 10000089, 10000023}
2、桶排序
桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的。
快速排序是将集合拆分为两个值域,这里称为两个桶,再分别对两个桶进行排序,最终完成排序。桶排序则是将集合拆分为多个桶,对每个桶进行排序,则完成排序过程。两者不同之处在于,快排是在集合本身上进行排序,属于原地排序方式,且对每个桶的排序方式也是快排。桶排序则是提供了额外的操作空间,在额外空间上对桶进行排序,避免了构成桶过程的元素比较和交换操作,同时可以自主选择恰当的排序算法对桶进行排序。
当然桶排序更是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。桶排序则是弱化了这种浪费情况,将最小值到最大值之间的每一个位置申请空间,更新为最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况。
2.1 算法思想
- 根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
- 遍历待排序集合,将每一个元素移动到对应的桶中;
- 对每一个桶中元素进行排序,并移动到已排序集合中。
步骤 3 中提到的已排序集合,和步骤 1、2 中的待排序集合是同一个集合。与计数排序不同,桶排序的步骤 2 完成之后,所有元素都处于桶中,并且对桶中元素排序后,移动元素过程中不再依赖原始集合,所以可以将桶中元素移动回原始集合即可
2.2 图解
2.3 算法性能分析
算法 | 平均时间 | 最优时间 | 最坏时间 | 额外空间 | 稳定性 |
简单选择排序 | O(n + k) | O(n + k) | O(n2) | n + k | 稳定 |
2.4 算法实现
/**
* 桶排序
*
* @param array
* @param bucketSize
* @return
*/
public static ArrayList<Integer> BucketSort(ArrayList<Integer> array, int bucketSize) {
if (array == null || array.size() < 2)
return array;
int max = array.get(0), min = array.get(0);
// 找到最大值最小值
for (int i = 0; i < array.size(); i++) {
if (array.get(i) > max)
max = array.get(i);
if (array.get(i) < min)
min = array.get(i);
}
int bucketCount = (max - min) / bucketSize + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
ArrayList<Integer> resultArr = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
bucketArr.add(new ArrayList<Integer>());
}
for (int i = 0; i < array.size(); i++) {
bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
}
for (int i = 0; i < bucketCount; i++) {
if (bucketSize == 1) { // 如果带排序数组中有重复数字时
for (int j = 0; j < bucketArr.get(i).size(); j++)
resultArr.add(bucketArr.get(i).get(j));
} else {
if (bucketCount == 1)
bucketSize--;
ArrayList<Integer> temp = BucketSort(bucketArr.get(i), bucketSize);
for (int j = 0; j < temp.size(); j++)
resultArr.add(temp.get(j));
}
}
return resultArr;
}
参考: