前言
之前学习了基于比较的各种排序算法java实现各种排序算法(比较排序),今天再学习一下非比较排序。
计数排序
代码实现
以对所有学生的成绩排序为例
import java.util.Arrays;
import java.util.Random;
public class CountingSort {
public void sort(int[] nums) {
//成绩的范围为[0,100]
int[] scores = new int[101];
for (int num : nums) {
scores[num]++;
}
int index = 0;
for (int score = 0; score < scores.length; score++) {
//scores[score]的值为score分数的学生人数
for (int i = 0; i < scores[score]; i++) {
nums[index++] = score;
}
}
}
public static void main(String[] args) {
//生成一个包含50个元素的数组(每个元素小于等于100)
int[] arr = generateRandomArr(50, 101);
System.out.println(Arrays.toString(arr));
new CountingSort().sort(arr);
System.out.println(Arrays.toString(arr));
}
private static int[] generateRandomArr(int n, int bound) {
Random random = new Random();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = random.nextInt(bound);
}
return arr;
}
}
统统计出每个成绩的学生个数,如60分学生有10个,61分学生有20个。计数排序适用于数据范围比较小的情况,如学生成绩,年龄等。时间复杂度O(N)。
应用
基数排序
import java.util.Arrays;
public class LSDSort {
public void sort(String[] arr) {
if (arr.length == 0) {
return;
}
int length = arr[0].length();
for (String s : arr) {
if (s.length() != length) {
throw new IllegalArgumentException("All Strings' length must be the same.");
}
}
int R = 256;
int[] counts = new int[R];
String[] temp = new String[arr.length];
int[] countIndexs = new int[R];
for (int r = length - 1; r >= 0; r--) {
//对每一位进行计数排序
Arrays.fill(counts, 0);
for (String s : arr) {
counts[s.charAt(r)]++;
}
for (int i = 0; i < R; i++) {
if (i == 0) {
countIndexs[i] = 0;
} else {
countIndexs[i] = countIndexs[i - 1] + counts[i - 1];
}
}
for (String s : arr) {
temp[countIndexs[s.charAt(r)]] = s;
countIndexs[s.charAt(r)]++;
}
System.arraycopy(temp, 0, arr, 0, arr.length);
}
}
public static void main(String[] args) {
String[] arr = {"BCA", "CAB", "ACB", "BAC", "ABC", "CBA"};
new LSDSort().sort(arr);
for (String s : arr) {
System.out.println(s);
}
}
}
从低位开始,对每一位进行计数排序,适用于等长字符串,如车牌号,手机号等。
桶排序
还是以对所有学生的成绩排序为例,分成10个桶,第一个桶成绩区间为[0-9],第二个为[10-19],依次类推
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class BucketSort {
public void sort(int[] nums) {
//成绩的范围为[0,100] 桶大小为10 11个桶
int bucketSize = 10;
int scoreRange = 101;
int bucketCount = scoreRange / bucketSize + (scoreRange % bucketSize == 0 ? 0 : 1);
List<Integer>[] bucketList = new List[bucketCount];
for (int i = 0; i < bucketList.length; i++) {
bucketList[i] = new LinkedList<>();
}
for (int num : nums) {
bucketList[getBucketIndex(num, bucketSize)].add(num);
}
List<Integer> res = new LinkedList<>();
for (List<Integer> list : bucketList) {
int[] array = list.stream().mapToInt(x -> x).toArray();
insertSort(array);
for (int val : array) {
res.add(val);
}
}
int index = 0;
for (Integer val : res) {
nums[index++] = val;
}
}
private int getBucketIndex(int num, int bucketSize) {
return num / bucketSize;
}
private void insertSort(int[] nums) {
//[0,i)为已排序的,[i,n)为未排序的
for (int i = 1; i < nums.length; i++) {
int insertValue = nums[i];
int j = i - 1;
while (j >= 0 && insertValue < nums[j]) {
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = insertValue;
}
}
public static void main(String[] args) {
int[] nums = {12, 56, 87, 99, 64, 5, 23};
new BucketSort().sort(nums);
System.out.println(Arrays.toString(nums));
}
}
学生成绩区间为[0,100],每个桶区间为10,对每个桶内元素进行插入排序,然后所有桶中元素顺序拿出来即可。桶排序适用于数据分布均匀的情况。
总结
- 计数排序可以看做桶排序的特例,当每个桶大小为1时,就变成了计数排序。
- 基数排序可以看做多轮计数排序,对每个有效位都进行一次计数排序。
- 这三种排序算法都需要额外的辅助空间,且对输入数据有要求,只能在特定情况下使用。