1. 桶排序
-
简单介绍
- 桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域;
- 拆分后形成的多个桶,从值域上看是处于有序状态的;
- 对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的;
-
桶排序过程中存在 2 个关键环节
- 元素值域的划分,也就是元素到桶的映射规则;映射规则需要根据待排序集合的元素分布特性进行选择:
- 若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上,则桶排序向比较性质排序算法演变。
- 若映射规则设计的过于具体、严苛,则可能导致待排序集合中每一个元素值映射到一个桶上,则桶排序向计数排序方式演化。
- 排序算法的选择;从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在对各个桶中元素进行排序时,可以自主选择合适的排序算法,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同。
- 元素值域的划分,也就是元素到桶的映射规则;映射规则需要根据待排序集合的元素分布特性进行选择:
-
算法过程:划分多个范围相同的区间,每个自区间自排序,最后合并
- 根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
- 遍历待排序集合,将每一个元素移动到对应的桶中;
- 对每一个桶中元素进行排序,并移动到已排序集合中。
-
图解过程
-
算法分析
- 时间复杂度
- 求最大最小值 n
- 桶初始化 k
- 遍历装桶 n
- 桶内排序 [n/k * lg(n/k)] * k
- 输出结果 n
- [sum] 3n + k + n/k * lg(n/k) * k = 3n + k + n * lg(n/k) ~ n + k
- [最坏] n^2
- [最好] n (n个桶而且值排列均匀)
- 空间复杂度
- n + k
- 实际上,如果空间做到最好的话,就只能用 {链表};但同时时间就做不到最好;反之亦然
- 稳定性
- 取决于桶内排序使用的算法
- 时间复杂度
2. 计数排序
- 计数排序是一个非基于比较的排序算法,这种排序算法不是基于元素比较,而是利用数组下标来确定元素的正确位置。
- 它的优势在于在对一定范围内的整数排序时,它的复杂度为 Ο(n+k)(其中 k 是整数的范围),快于任何比较排序算法。
3. 标配
- 平均时间复杂度:O(n+k)
- 最坏时间复杂度:O(n+k)
- 最好时间复杂度:O(n+k)
- 空间复杂度:O(n+k)
- 稳定
4. 举例理解
- 这就是计数排序的基本过程,它适用于一定范围的整数排序。在取值范围不是很大的情况下,它的性能在某些情况甚至快过那些 O(nlogn) 的排序,例如快速排序、归并排序
- 这是一种牺牲空间换取时间的做法,但当
O(k) > O(nlog(n))
的时候其效率反而不如基于比较的排序 // 基于比较的排序的时间复杂度在理论上的下限是 O(nlog(n)), 如归并排序、堆排序
5. 适用场景
- 计数排序看上去很强大,但是它存在两大局限性:
- 当数列最大最小值差距过大时,并不适用于计数排序
- 当数列元素不是整数时,并不适用于计数排序
- 只适用于特定问题,也就是对源数据有要求:量大但是范围小
- 某大型企业数万名员工按年龄排序
- 如何快速得知高考名次(不让用排序)
6. 代码实现
6.1 桶排序
public static void bucketSort(int[] arr) {
// 1. 确定数据的范围
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
// 2. 计算桶的个数, 划分桶
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++)
bucketArr.add(new ArrayList<Integer>());
// 3. 将元素放入桶中
for (int i = 0; i < arr.length; i++) {
int num = (arr[i] - min) / arr.length;
bucketArr.get(num).add(arr[i]);
}
// 4. 桶内排序
for (int i = 0; i < bucketArr.size(); i++) {
Collections.sort(bucketArr.get(i));
System.out.println(bucketArr.get(i));
}
// 5. 将桶中元素赋值到原序列
for (int i = 0, index = 0; i < bucketArr.size(); i++)
for (int j = 0; j < bucketArr.get(i).size(); j++)
arr[index++] = bucketArr.get(i).get(j);
}
6.2 计数排序
public class CountingSort {
public static void main(String[] args) {
// int[] arr1 = {9, 3, 5, 4, 9, 1, 2, 7, 8, 1, 3, 6, 5, 3, 4, 0, 10, 9, 7, 9};
int[] arr23 = new int[200];
for (int i = 0; i < arr23.length; i++)
arr23[i] = (int) (Math.random()*50) + 150;
System.out.println("原始数组:");
print(arr23);
int[] result = sort_3(arr23);
System.out.println("结果数组:");
print(result);
}
// 解决稳定性问题:① 计数数组 → 累加数组;② 反向遍历原始数组
public static int[] sort_3(int[] arr) { // O(n+k)
// 找最大值 和 最小值
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) { // O(n)
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
// 计数数组 {3, 5, 2}
int[] count = new int[max-min+1];
for (int i = 0; i < arr.length; i++) // O(n)
count[arr[i]-min]++;
// 计数数组 → 累加数组 {3, 8, 10}
for (int i = 1; i < count.length; i++) // O(k)
count[i] = count[i] + count[i-1];
// 利用[累加数组]对原始数组{0101212011} 反向遍历生成结果数组
int[] result = new int[arr.length];
for (int i = arr.length-1; i >= 0; i--) // O(n)
result[--count[arr[i]-min]] = arr[i];
return result;
}
// 局限性:不稳定
public static int[] sort_2(int[] arr) { // O(n+k)
// 找最大值 和 最小值
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) { // O(n)
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
// 计数数组
int[] count = new int[max - min + 1];
for (int i = 0; i < arr.length; i++)
count[arr[i]-min]++;
// 组织结果数组
int[] result = new int[arr.length];
for (int i = 0, index = 0; i < count.length; i++) // O(k)
while (count[i]-- > 0)
result[index++] = i + min;
return result;
}
// 局限性:① 数据起始范围必须从 0 开始,不然就会有空间浪费;② 不稳定
public static int[] sort_1(int[] arr) {
// 找最大值
int max = arr[0];
for (int i = 1; i < arr.length; i++)
if (max < arr[i]) max = arr[i];
// 计数数组(0 也是数据噻, 所以要 +1)
int[] count = new int[max + 1];
for (int i = 0; i < arr.length; i++)
count[arr[i]]++;
// 组织结果数组
int[] result = new int[arr.length];
for (int i = 0, index = 0; i < count.length; i++)
while (count[i]-- > 0)
result[index++] = i;
return result;
}
public static void print(int[] arr) {
for(int i = 0; i < arr.length; i++)
System.out.print(arr[i] + " ");
System.out.println();
}
}