1.简介
非比较排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
计数排序 | O(n+k) | O(n+k) | 稳定 |
桶排序 | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(d*(n+k)) | O(n+k) | 稳定 |
其中
-
n 是数组长度
-
k 是桶长度
-
d 是基数位数
2.计数排序
方法1(简化后的计数排序)
public static void sort(int[] a) {
int min = a[0];
int max = a[0];
for (int i : a) {
if (i > max) {
max = i;
} else if (i < min) {
min = i;
}
}
int[] counting = new int[max - min + 1];
for (int i : a) {
counting[i - min]++;
}
int k = 0;
for (int i = 0; i < counting.length; i++) {
while (counting[i] > 0) {
a[k] = i + min;
counting[i]--;
k++;
}
}
}
针对 byte [],因为数据范围已知,省去了求最大、最小值的过程,java 中对 char[]、short[]、byte[] 的排序都可能采用 counting 排序
public static void sort(byte[] a) {
int[] counting = new int[256];
for (int i : a) {
counting[i & 0xFF]++;
}
int k = a.length-1;
for (int i = 128 + 256; k >= 0; ) {
while (counting[--i & 0xFF] ==0);
int v = i & 0xFF;
int c = counting[i & 0xFF];
for (int j = 0; j < c; j++) {
a[k] = (byte) v;
k--;
}
}
}
稳定计数排序
public static void sort2(int[] a) {
int min = a[0];
int max = a[0];
for (int i : a) {
if (i > max) {
max = i;
} else if (i < min) {
min = i;
}
}
int[] counting = new int[max - min + 1];
for (int i : a) {
counting[i - min]++;
}
for (int i = 1; i < counting.length; i++) {
counting[i] = counting[i] + counting[i - 1];
}
int[] b = new int[a.length];
for (int i = a.length - 1; i >= 0; i--) {
int j = a[i] - min;
counting[j]--;
b[counting[j]] = a[i];
}
System.arraycopy(b, 0, a, 0, a.length);
}
3.桶排序
初步实现
public class BucketSort {
public static void main(String[] args) {
int[] ages = {20, 18, 66, 25, 67, 30}; // 假设人类年龄 1~99 那么分为10个桶
System.out.println(Arrays.toString(ages));
sort(ages);
System.out.println(Arrays.toString(ages));
}
public static void sort(int[] a) {
DynamicArray[] buckets = new DynamicArray[10];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new DynamicArray();
}
for (int v : a) {
DynamicArray bucket = buckets[v / 10];
bucket.addLast(v);
}
for (DynamicArray bucket : buckets) {
System.out.println(Arrays.toString(bucket.array()));
}
int k = 0;
for (DynamicArray bucket : buckets) {
int[] array = bucket.array();
InsertionSort.sort(array);
for (int v : array) {
a[k++] = v;
}
}
}
}
通用
public class BucketSortGeneric {
public static void main(String[] args) {
int[] ages = {20, 10, 28, 66, 25, 31, 67, 30, 70}; // 假设人类年龄 1~99
System.out.println(Arrays.toString(ages));
sort(ages, 20);
System.out.println(Arrays.toString(ages));
}
public static void sort(int[] a, int range) {
int max = a[0];
int min = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max) {
max = a[i];
}
if (a[i] < min) {
min = a[i];
}
}
// 1. 准备桶
DynamicArray[] buckets = new DynamicArray[(max - min) / range + 1];
System.out.println(buckets.length);
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new DynamicArray();
}
// 2. 放入年龄数据
for (int age : a) {
buckets[(age - min) / range].addLast(age);
}
int k = 0;
for (DynamicArray bucket : buckets) {
// 3. 排序桶内元素
int[] array = bucket.array();
InsertionSort.sort(array);
System.out.println(Arrays.toString(array));
// 4. 把每个桶排序好的内容,依次放入原始数组
for (int v : array) {
a[k++] = v;
}
}
}
}
4.基数排序
public class RadixSort {
public static void radixSort(String[] a, int length) {
ArrayList<String>[] buckets = new ArrayList[128];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (int i = length - 1; i >= 0 ; i--) {
for (String s : a) {
buckets[s.charAt(i)].add(s);
}
int k = 0;
for (ArrayList<String> bucket : buckets) {
for (String s : bucket) {
a[k++] = s;
}
bucket.clear();
}
}
}
public static void main(String[] args) {
/*String[] phoneNumbers = new String[10];
phoneNumbers[0] = "13812345678";
phoneNumbers[1] = "13912345678";
phoneNumbers[2] = "13612345678";
phoneNumbers[3] = "13712345678";
phoneNumbers[4] = "13512345678";
phoneNumbers[5] = "13412345678";
phoneNumbers[6] = "15012345678";
phoneNumbers[7] = "15112345678";
phoneNumbers[8] = "15212345678";
phoneNumbers[9] = "15712345678";*/
String[] phoneNumbers = new String[10];
phoneNumbers[0] = "138";
phoneNumbers[1] = "139";
phoneNumbers[2] = "136";
phoneNumbers[3] = "137";
phoneNumbers[4] = "135";
phoneNumbers[5] = "134";
phoneNumbers[6] = "150";
phoneNumbers[7] = "151";
phoneNumbers[8] = "152";
phoneNumbers[9] = "157";
RadixSort.radixSort(phoneNumbers, 3);
for (String phoneNumber : phoneNumbers) {
System.out.println(phoneNumber);
}
}
}
基数排序是稳定排序,因此先排个位、再排十位,十位的排序不会打乱个位取值相等的元素顺序
5.Java中的排序
Arrays.sort
JDK 7~13 中的排序实现
排序目标 | 条件 | 采用算法 |
---|---|---|
int[] long[] float[] double[] | size < 47 | 混合插入排序 (pair) |
size < 286 | 双基准点快排 | |
有序度高 | 归并排序 | |
有序度低 | 双基准点快排 | |
byte[] | size > 29 | 计数排序 |
size <= 29 | 插入排序 | |
char[] short[] | size > 3200 | 计数排序 |
size < 47 | 插入排序 | |
size < 286 | 双基准点快排 | |
有序度高 | 归并排序 | |
有序度低 | 双基准点快排 | |
Object[] | -Djava.util.Arrays.useLegacyMergeSort=true | 传统归并排序 |
TimSort |
JDK 14~20 中的排序实现
排序目标 | 条件 | 采用算法 |
---|---|---|
int[] long[] float[] double[] | size < 65 并不是最左侧 | 混合插入排序 (pin) |
size < 44 并位于最左侧 | 插入排序 | |
递归次数超过 384 | 堆排序 | |
对于整个数组或非最左侧 size > 4096,有序度高 | 归并排序 | |
有序度低 | 双基准点快排 | |
byte[] | size > 64 | 计数排序 |
size <= 64 | 插入排序 | |
char[] short[] | size > 1750 | 计数排序 |
size < 44 | 插入排序 | |
递归次数超过 384 | 计数排序 | |
不是上面情况 | 双基准点快排 | |
Object[] | -Djava.util.Arrays.useLegacyMergeSort=true | 传统归并排序 |
TimSort |
-
其中 TimSort 是用归并+二分插入排序的混合排序算法
-
值得注意的是从 JDK 8 开始支持 Arrays.parallelSort 并行排序
-
根据最新的提交记录来看 JDK 21 可能会引入基数排序等优化