(极客时间专栏的总结)
桶排序 (线性)
非基于比较的算法, 都不涉及元素之间的比价操作.
桶排序的核心思想是: 将要排序的数据分到几个有序的桶里, 每个桶里的数据再单独进行排序. 桶内排完序之后, 再把每个桶里的数据按照顺序依次取出, 组成的序列就是有序的了.
如果要排序的数据有 n 个, 我们把他们均匀地划分到 m 个桶内, 每个桶里就有 k = n/m 个数据. 每个桶内部使用快排, 时间复杂度 O(klogk), m 个桶排序的时间复杂度就是 O(m*klogk) = O(nlogn/m), m 非常接近 n 时, O(n)
桶排序对数据的要求是非常苛刻的
首先, 要排序的数据需要很容易就能划分成 m 个桶, 并且, 桶与桶之间有着天然的大小排序. 这样, 每个桶内的数据排完序之后, 桶与桶之间的数据就不需要再进行排序了.
其次, 数据在各个桶之间的分布要求是比较均匀的. 否则在极端情况下, 所有数据都放到一个桶中, 排序会退化成快排 O(nlogn)
应用场景:
桶排序比较适合用在外部排序中, 外部排序就是数据存储在外部磁盘中, 数据量比较大, 内存有限, 无法将数据全部加载到内存中.
比如对 10GB 的订单按照金额进行排序, 可以先扫描一遍文件, 将一定范围的金额放到一个桶内, 一个桶对应一个文件. 排好序后, 只需要按照文件编号, 从小到大依次将每个订单写入到一个文件中, 这时这个文件中存储的订单顺序就是从小到大有序的了.
计数排序
计数排序其实是桶排序的一种特殊情况. 当数据最大值是 k 时, 直接划分 k 个桶, 这样省掉了桶内排序的时间.
时间复杂度 O(n + k), 需要存储空间 O(n + k)
**应用场景: **
计数排序只能用在数据范围不大的场景中, 如果数据范围 k 比要排序的数据 n 大很多 (非常分散), 就不适合计数排序了.
而且, 计数排序只能给非负整数排序. 其他类型的数据要在不改变相对大小的情况下映射到非负整数.
不稳定的
public static void bucketSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
//找出数组中最大值
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
//创建 max + 1 个桶, 并添加
int[] bucket = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
int i = 0;
//不稳定的取数方法
for (int j = 0; j < bucket.length; j++) {
while (bucket[j] > 0) {
arr[i++] = j;
bucket[j]--;
}
}
}
稳定的
public static void bucketSort(int[] arr) {
//因为计数排序是桶排的特殊形式, 这里数组名定义为bucket
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
//找出数组中最大值
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
//创建 max + 1 个桶, 并添加
int[] bucket = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
//依次累加
for (int i = 1; i < bucket.length; i++) {
bucket[i] = bucket[i - 1] + bucket[i];
//此时bucket存储 <= 分数i的考生个数
}
//临时数组 help, 存储排序之后的结果
int[] help = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int index = bucket[arr[i]] - 1;
help[index] = arr[i];
bucket[arr[i]]--;
}
//将结果复制回arr
for (int i = 0; i < arr.length; i++) {
arr[i] = help[i];
}
}
基数排序
怎么将10w个手机号码从小到大排序?
先按照最后一位排序, 再按照倒数第二位重新排序…最后按照第一位重新排序.
根据每一位来排序, 可以用计数(桶)排序的方法, O(n), 有 k 位, 时间复杂度是 O(k*n), 稳定
(如果位数不等长可以补0)
**应用场景: **
基数排序对要排序的数据是有要求的, 需要可以分割出独立的比较单元, 位. 而且位之间有递进的关系.
每一位的数据范围不能太大, 要可以用线性排序算法来排序, 否则, 基数排序的时间复杂度就无法做到 O(n) 了
对扑克牌排序
设置4个桶, 分别存红桃,黑桃, 梅花, 方片, 桶内顺序排序.
取出时, 依次取出4个桶中最大的数.
总结
桶排序应用场景:
- 要排序的数据需要很容易就能划分成 m 个桶
- 桶与桶之间有着天然的大小排序
- 数据在各个桶之间要求分布均匀
计数排序应用场景
- 数据范围不大, 不过于分散
- 只能给非负整数排序
基数排序应用场景
- 数据可以分割出独立的比较单元, 位
- 位之间有递进的关系
- 数据范围不能太大 (用基数排序来按位排序)