计数排序
非常简单,但是数值范围比较大了就不行了
算法思想:
假如有150个数字在 0-100之间需要排序
按 0,1,2,3,.....100 数字放101个桶,每次遇见相应的数字吧它放到桶里去。
最后从0,1,2.....100依次出桶就有序了。
基数排序
算法思想:
非负数排序:先按个位数排序,再按十位数排序....
如果有负数:找出最小负数,所有数都-最小负数,然后就转换成非负数排序。最后再所有数都加最小负数
代码如下:
public static void sort() {
// 如果会溢出,那么要改用long类型数组来排序
// 找到数组中的最小值
int min = arr[0];
for (int i = 1; i < n; i++) {
min = Math.min(min, arr[i]);
}
int max = 0;
for (int i = 0; i < n; i++) {
// 数组中的每个数字,减去数组中的最小值,就把arr转成了非负数组
arr[i] -= min;
// 记录数组中的最大值
max = Math.max(max, arr[i]);
}
// 根据最大值在BASE进制下的位数,决定基数排序做多少轮
radixSort(bits(max));
// 数组中所有数都减去了最小值,所以最后不要忘了还原
for (int i = 0; i < n; i++) {
arr[i] += min;
}
}
// 返回number在BASE进制下有几位
public static int bits(int number) {
int ans = 0;
while (number > 0) {
ans++;
number /= BASE;
}
return ans;
}
// 基数排序核心代码
// arr内要保证没有负数
// m是arr中最大值在BASE进制下有几位
public static void radixSort(int bits) {
// 理解的时候可以假设BASE = 10
for (int offset = 1; bits > 0; offset *= BASE, bits--) {
Arrays.fill(cnts, 0);
for (int i = 0; i < n; i++) {
// 数字提取某一位的技巧
cnts[(arr[i] / offset) % BASE]++;
}
for (int i = 1; i < BASE; i++) {
cnts[i] = cnts[i] + cnts[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
// 前缀数量分区的技巧
// 数字提取某一位的技巧
help[--cnts[(arr[i] / offset) % BASE]] = arr[i];
}
for (int i = 0; i < n; i++) {
arr[i] = help[i];
}
}
}
基数排序的实现细节,非常优雅的一个实现
关键点:前缀数量分区的技巧、数字提取某一位的技巧
时间复杂度O(n),额外空间复杂度O(m),需要辅助空间做类似桶的作用,来不停的装入、弹出数字
一般来讲,计数排序要求,样本是整数,且范围比较窄
一般来讲,基数排序要求,样本是10进制的非负整数
如果不是就需要转化,代码里做了转化,并且代码里可以设置任何进制来进行排序
一旦比较的对象不再是常规数字,那么改写代价的增加是显而易见的,所以不基于比较的排序并不通用