计数排序(Counting Sort)是一种线性时间复杂度的排序算法,适用于整数数据。它的基本思想是将输入的数据值转化为键存储在额外开辟的数组空间中。计数排序不是基于比较的排序算法,因此它可以突破 O(nlogn) 的时间下界,实现线性时间排序。
算法原理
计数排序的工作原理如下:
- 找出待排序数组的最大值和最小值:确定统计数组的长度(最大值与最小值之差+1)。
- 初始化统计数组:创建一个长度为最大值与最小值之差+1的新数组,并将其所有元素初始化为0。
- 统计频率:遍历待排序数组,对于每个元素,统计其出现的次数,更新到统计数组对应的位置。
- 根据计数得到排序结果:遍历统计数组,按照顺序将元素放置到结果数组中。对于统计数组中每个位置的值,表示待排序数组中该位置元素出现的次数,将该元素重复放置到结果数组相应位置的次数。
算法实现
以下是计数排序的Java实现示例:
public class CountingSortExample {
public static void main(String[] args) {
int[] arr = {4, 2, 2, 8, 3, 3, 1, 0};
countingSort(arr);
System.out.println("Sorted array: ");
for (int i : arr) {
System.out.print(i + " ");
}
}
public static void countingSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
// 找出数组的最大值和最小值
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
// 初始化计数数组
int range = max - min + 1;
int[] count = new int[range];
for (int i = 0; i < arr.length; i++) {
count[arr[i] - min]++;
}
// 根据计数数组得到排序结果
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
}
}
算法适用性
计数排序适用于以下场景:
- 待排序数组中的元素是整数,且范围不是很大。
- 待排序数组中的元素重复次数较多,且分布较为集中。
算法优缺点
优点:
- 计数排序可以实现线性时间复杂度 O(n+k),其中 n 是待排序数组的长度,k 是数组中的最大值与最小值之差。
- 排序过程中不需要元素之间的比较操作,适合于比较成本较高的情况。
缺点:
- 计数排序需要额外的空间来存储计数数组,空间复杂度较高。
- 当元素个数非常大时,计数排序可能需要较长的预处理时间和额外空间。
计数排序是一种简单且高效的排序算法,特别适合于特定场景。在实际应用中,可以根据数据的特点和需求来选择是否使用计数排序。
题目 1:基本计数排序
描述:
给定一个整数数组 arr
,你需要使用计数排序算法对其进行升序排序。
要求:
- 不使用任何现成的排序库或函数。
- 考虑数组中可能包含的负数。
示例:
输入:[-2, 0, 2, 2, 3, 3, 4, 5, -1]
输出:[-2, -1, 0, 2, 2, 3, 3, 4, 5]
Java 源码:
import java.util.Arrays;
public class CountingSortExample1 {
public static void main(String[] args) {
int[] arr = {-2, 0, 2, 2, 3, 3, 4, 5, -1};
countingSort(arr);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
public static void countingSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
Arrays.fill(count, 0);
for (int num : arr) {
count[num - min]++;
}
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
}
}
题目 2:限制范围内的计数排序
描述:
给定一个整数数组 arr
和一个范围 [min, max]
,你需要使用计数排序算法对数组中在该范围内的元素进行排序,范围外的元素保持不变。
要求:
- 只对给定范围内的元素进行计数排序。
- 保持原数组中元素的相对顺序。
示例:
输入:[-5, -2, 0, 2, 4, 6, 7, 8, 1]
,范围 1, 5
输出:[-5, -2, 1, 2, 4, 5, 6, 7, 8]
Java 源码:
public class CountingSortExample2 {
public static void main(String[] args) {
int[] arr = {-5, -2, 0, 2, 4, 6, 7, 8, 1};
int min = 1;
int max = 5;
countingSort(arr, min, max);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
public static void countingSort(int[] arr, int min, int max) {
if (arr == null || arr.length == 0) {
return;
}
int range = max - min + 1;
int[] count = new int[range];
Arrays.fill(count, 0);
for (int num : arr) {
if (num >= min && num <= max) {
count[num - min]++;
}
}
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
if (index < arr.length && arr[index] == (i + min)) {
index++;
} else {
arr[index++] = i + min;
count[i]--;
}
}
}
}
}
题目 3:多位数计数排序
描述:
给定一个非负整数数组 arr
,你需要使用计数排序算法对其进行按位排序。首先按照个位数排序,然后按照十位数排序,以此类推,直到所有位数都排序完成。
要求:
- 排序过程中保持相同位数的数字的相对顺序。
- 不使用除法和乘法操作。
示例:
输入:[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
输出:[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
Java 源码:
import java.util.Arrays;
public class CountingSortExample3 {
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
multiDigitCountingSort(arr);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
public static void multiDigitCountingSort(int[] arr) {
int n = arr.length;
int[] count = new int[10];
int[] output = new int[n];
int exp = 1; // 10^0
while (exp < n) {
// 计算频率数组
Arrays.fill(count, 0);
for (int i = 0; i < n; i++) {
int digit = (arr[i] / exp) % 10;
count[digit]++;
}
// 累计频率
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 根据频率数组得到排序结果
for (int i = n - 1; i >= 0; i--) {
int digit = (arr[i] / exp) % 10;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
// 将输出数组复制回原数组,并更新exp
System.arraycopy(output, 0, arr, 0, n);
exp *= 10;
}
}
}
这些题目和代码示例可以帮助你更好地理解和掌握计数排序算法,以及如何在实际问题中应用这一算法。在面试中,你可能会遇到类似的问题,希望这些示例能够帮助你做好准备。