这三种排序都不是基于比较的排序,典型的空间换时间算法。
0. 修改父类
因为父类中针对稳定性判断时泛型的其它类,但是下方的排序只支持整数(浮点型等非自定义类型)。
private boolean isStable(){
if (this instanceof CountingSortPro) return true;
if (this instanceof CountingSort) return true;
if (this instanceof ShellSort) return false;
if (this instanceof RadixSort) return true;
Student[] students = new Student[20];
for (int i = 0; i < students.length; i++) {
students[i] = new Student(i * 10, 10);
}
sort((T[]) students);
for (int i = 1; i < students.length; i++) {
int score = students[i].score;
int prevScore = students[i - 1].score;
if (score != prevScore + 10) return false;
}
return true;
}
1. 计数排序(Counting Sort)
1954年提出,适合对一定范围内的整数进行排序。
1.1 简单版本实现
核心思想:统计每个整数在序列中出现的次数,进而推导每个整数在有序序列中的索引。
- 原数组
- 新建一个数组存储整数出现的次数:
- 推导结果:
public class CountingSort extends Sort<Integer> {
@Override
protected void sort() {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
int[] counts = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
counts[arr[i]]++;
}
int index = 0;
for (int i = 0; i < counts.length; i++) {
while (counts[i] > 0) {
arr[index] = i;
index++;
counts[i]--;
}
}
}
}
1.2 优化思路
-
缺点:在上方1.1版本中存在以下问题:
- 不是稳定性排序;
- 消耗内存过大;
- 不能对负整数进行排序。
-
优化内存空间和负整数排序:获取最小值,将内存空间的大小从[0,max] 减少到[min,max] ;这种实现也达到了可以对负整数优化的目的,不再以元素值对应索引值,而是以顺序对应索引。得出结论:元素k在counts 中的索引值为:k - min -
稳定性排序:每个次数加上前面所有元素出现的次数,可以得到当前元素在有序序列种的位置。 -
完成排序:得出结论:元素k在有序序列中的索引为:counts[ k - min ] - p 。p为倒数第几个k。 例如:索引为 6 的 7 是倒数第一个 7 ,在counts 中的索引为: 7 - 3 = 4 ;在有序序列中的索引为:7 - 1 = 6 。
1.3 优化实现
- 从后往前遍历:
- 使用相应的值计算出索引之后,下方
counts 序列中也执行减一,这样可以做到稳定性。 - 然后将值放入到有序序列中:
public class CountingSortPro extends Sort<Integer> {
@Override
protected void sort() {
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
if (arr[i] < min){
min = arr[i];
}
}
int[] counts = new int[max - min + 1];
for (Integer integer : arr) {
counts[integer - min]++;
}
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i - 1];
}
int[] newArr = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int index = --counts[arr[i] - min];
newArr[index] = arr[i];
}
for (int i = 0; i < newArr.length; i++) {
arr[i] = newArr[i];
}
}
}
1.4 总结分析
- 时间复杂度:最好、最坏、平均:
O(n + k) 。 - 空间复杂度:
O(n + k) 。 - 属于稳定排序。
1.5 自定义对象实现计数排序
如果自定义的对象包含可共排序的整数类型,也可以使用计数排序。
@Data
@AllArgsConstructor
@ToString
public class Person {
private Integer id;
private String name;
}
public class CountingSortObj{
public static void main(String[] args) {
Person[] arr = {new Person(1,"aa")
,new Person(-1,"bb"),
new Person(99,"cc"),
new Person(5,"dd"),
new Person(0,"ee"),};
sort(arr);
for (Person person : arr) {
System.out.println(person);
}
}
private static void sort(Person[] arr) {
int max = arr[0].getId();
int min = arr[0].getId();
for (int i = 1; i < arr.length; i++) {
if (arr[i].getId() > max){
max = arr[i].getId();
}
if (arr[i].getId() < min){
min = arr[i].getId();
}
}
int[] counts = new int[max - min + 1];
for (Person person : arr) {
counts[person.getId() - min]++;
}
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i - 1];
}
Person[] newArr = new Person[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int index = --counts[arr[i].getId() - min];
newArr[index] = arr[i];
}
System.arraycopy(newArr, 0, arr, 0, newArr.length);
}
}
2. 基数排序(Radix Sort)
非常适合用于整数排序,特别是非负整数。
2.1 执行流程
依次对个位数、十位数、百位数…进行排序(从低位到高位)。
- 原始数组
- 对个位数进行排序后
- 再对十位数进行排序,没有就为0
- 最后对百位数再进行排序:
2.2 实现
因为每次针对位数进行排序时,只有 0~9 ,非常符合使用计数排序。 所以这里整合计数排序进行实现。
public class RadixSort extends Sort<Integer> {
@Override
protected void sort() {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
for (int divider = 1; divider <= max; divider *= 10) {
countingSort(divider);
}
}
private void countingSort(int divider){
int[] counts = new int[10];
for (Integer integer : arr) {
counts[integer / divider % 10]++;
}
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i - 1];
}
int[] newArr = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int index = --counts[arr[i] / divider % 10];
newArr[index] = arr[i];
}
for (int i = 0; i < newArr.length; i++) {
arr[i] = newArr[i];
}
}
}
2.3 复杂度分析
- 最好、最坏、平均复杂度:
O(d * (n + k)) ,d 是最大值的位数,k 是进制数; - 空间复杂度:
O( n + k ) ,k 是进制数; - 属于稳定排序。
3. 桶排序(Bucket Sort)
3.1 执行流程
- 创建一定数量的桶(数组、或者链表);
- 按照一定规则将序列中的元素均匀的分配到对应的桶;
- 分别对桶单独排序;
- 将所有的非空桶元素合并成有序序列。
3.2 实现
public class BucketSort {
public static void main(String[] args) {
Double[] arr = {0.34, 0.47, 0.29, 0.84, 0.45, 0.38, 0.35, 0.76};
bucketSort(arr);
for (Double aDouble : arr) {
System.out.print(aDouble + "_");
}
}
public static void bucketSort(Double[] arr){
List<Double>[] buckets = new List[arr.length];
for (Double info : arr) {
int bucketIndex = (int) (info * arr.length);
List<Double> bucket = buckets[bucketIndex];
if (bucket == null){
bucket = new LinkedList<>();
buckets[bucketIndex] = bucket;
}
bucket.add(info);
}
int index = 0;
for (List<Double> bucket : buckets) {
if (bucket == null) continue;
bucket.sort(null);
for (Double aDouble : bucket) {
arr[index++] = aDouble;
}
}
}
}
3.3 复杂度分析
- 空间复杂度:
O(n + m) ,m 是桶的数量; - 时间复杂度:
O( n + k) ; - 属于稳定排序
|