1. 概述

在本篇文章中,我们将探讨如何高效地对一个包含小整数的数组进行排序。我们会先明确问题定义,然后通过一个示例进行说明。最后,我们介绍两种适用场景:一种是针对小整数数组的排序,另一种是适用于数组中任意两个元素之间绝对差值较小的情况。

这篇文章的核心是 计数排序(Counting Sort) 及其变种,它非常适合在已知数值范围较小的前提下进行排序,时间复杂度优于通用排序算法(如快速排序、归并排序等)。


2. 问题定义

假设我们有一个整数数组 A,包含 N 个较小的整数值。我们的目标是尽可能高效地将它排序。

举个例子:

给定数组 A = [2, 5, 1, 4, 3],我们希望将其重新排列为 A = [1, 2, 3, 4, 5]。

也就是说,我们希望数组满足如下条件:

A[i] ≥ A[i - 1],其中 i 的取值范围是 1 ≤ i < N。

这个排序问题是一个特殊场景下的排序问题。由于数组中的值较小,我们可以利用这一点来选择更高效的排序算法。


3. 计数排序方法

3.1 核心思想

核心思想:统计每个数字出现的次数,然后按从小到大的顺序将它们依次写入新数组中。

具体来说:

  • 遍历原始数组 A,统计每个数字的出现次数;
  • 然后从 0 到最大值依次遍历,将每个数字按照其出现次数依次写入结果数组;
  • 最终得到的数组就是排序后的结果。

这个方法非常适合小整数数组,因为我们可以用一个固定大小的数组来统计频率,而无需使用哈希表等复杂结构。

3.2 实现代码

public static int[] countingSort(int[] A) {
    if (A == null || A.length == 0) return new int[0];

    int max = Arrays.stream(A).max().getAsInt();
    int[] count = new int[max + 1];

    // Step 1: 统计每个数字出现的次数
    for (int num : A) {
        count[num]++;
    }

    // Step 2: 生成排序后的数组
    int[] sorted = new int[A.length];
    int idx = 0;
    for (int i = 0; i <= max; i++) {
        while (count[i]-- > 0) {
            sorted[idx++] = i;
        }
    }

    return sorted;
}

3.3 时间复杂度分析

✅ **时间复杂度:O(N + M)**,其中:

  • N 是数组 A 的长度;
  • M 是数组中最大值。

⚠️ 注意: 如果数组中最大值非常大(比如 10^6),那么这种方法会占用大量内存,效率反而会下降。所以它只适用于数值范围较小的数组。


4. 小绝对差值情况下的排序方法

4.1 核心思想

核心思想:通过偏移量将所有元素调整为非负数,从而缩小计数数组的大小。

当数组中元素之间的绝对差值较小时,我们可以:

  • 找出数组中的最小值作为偏移量 offset;
  • 将每个元素减去 offset,变成非负数;
  • 使用计数排序对这些偏移后的值进行排序;
  • 最后加上 offset,还原为原始值。

这样做的好处是:我们不需要为非常大的最大值分配额外空间。

4.2 实现代码

public static int[] countingSortWithSmallAbsoluteDifference(int[] A) {
    if (A == null || A.length == 0) return new int[0];

    int min = Arrays.stream(A).min().getAsInt();
    int max = Arrays.stream(A).max().getAsInt();

    int range = max - min + 1;
    int[] count = new int[range];

    // Step 1: 偏移后统计频率
    for (int num : A) {
        count[num - min]++;
    }

    // Step 2: 生成排序后的数组
    int[] sorted = new int[A.length];
    int idx = 0;
    for (int i = 0; i < range; i++) {
        while (count[i]-- > 0) {
            sorted[idx++] = i + min;
        }
    }

    return sorted;
}

4.3 时间复杂度分析

✅ **时间复杂度:O(N + D)**,其中:

  • N 是数组 A 的长度;
  • D 是数组中最大值与最小值的差值(即 max(A) - min(A))。

⚠️ 注意: 如果 D 很小,比如几十或几百,这个方法非常高效。即使数组中存在负数,也可以通过偏移量处理,不影响排序效率。


5. 总结

本文我们介绍了两种适用于小整数数组的排序方法:

计数排序(Counting Sort)

  • 适合数组元素值较小的情况;
  • 时间复杂度 O(N + M),其中 M 是最大值;
  • 缺点是不能处理大范围数值。

带偏移量的计数排序(Offset Counting Sort)

  • 适合数组中任意两个元素的绝对差值较小的情况;
  • 时间复杂度 O(N + D),其中 D 是最大值与最小值之差;
  • 可以处理负数。

这两种方法都比通用排序算法(如快速排序、归并排序)在特定场景下更快,尤其适合数据范围有限的整数数组排序。

如果你在实际项目中遇到这类问题,可以优先考虑使用这些方法,能显著提升性能。


原始标题:What Is the Best Sorting Algorithm To Sort an Array of Small Integers