【java实现+4种变体完整例子】排序算法中【计数排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是计数排序的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格:


一、计数排序基础实现

原理

通过统计每个元素的出现次数,按顺序累加得到每个元素的最终位置,并填充到结果数组中。

代码示例
java 复制代码
public class CountingSort {
    void sort(int[] arr) {
        if (arr.length == 0) return;

        // 找到数据范围
        int min = Arrays.stream(arr).min().getAsInt();
        int max = Arrays.stream(arr).max().getAsInt();
        int range = max - min + 1;

        // 统计每个元素出现的次数
        int[] count = new int[range];
        for (int num : arr) {
            count[num - min]++;
        }

        // 累加统计结果,得到每个元素的最终位置
        for (int i = 1; i < count.length; i++) {
            count[i] += count[i - 1];
        }

        // 反向填充结果数组以保持稳定性
        int[] output = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int num = arr[i];
            output[count[num - min] - 1] = num;
            count[num - min]--;
        }

        // 将结果复制回原数组
        System.arraycopy(output, 0, arr, 0, arr.length);
    }
}
复杂度分析
  • 时间复杂度O(n + k)n为元素数量,k为值域范围)。
  • 空间复杂度O(k)
  • 稳定性:稳定(相同值的元素相对顺序不变)。

二、常见变体及代码示例

1. 支持负数的计数排序

改进点 :通过偏移量处理负数,扩展适用范围。
适用场景:数据包含负值。

java 复制代码
public class CountingSortWithNegatives {
    void sort(int[] arr) {
        if (arr.length == 0) return;

        int min = Arrays.stream(arr).min().getAsInt();
        int max = Arrays.stream(arr).max().getAsInt();
        int range = max - min + 1;

        int[] count = new int[range];
        for (int num : arr) {
            count[num - min]++; // 偏移量为min
        }

        for (int i = 1; i < count.length; i++) {
            count[i] += count[i - 1];
        }

        int[] output = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int num = arr[i];
            output[count[num - min] - 1] = num;
            count[num - min]--;
        }

        System.arraycopy(output, 0, arr, 0, arr.length);
    }
}
2. 原地计数排序

改进点 :尝试在原数组上操作,减少额外空间。
适用场景:内存受限场景(但可能牺牲时间效率)。

java 复制代码
public class InPlaceCountingSort {
    void sort(int[] arr) {
        if (arr.length == 0) return;

        int min = Arrays.stream(arr).min().getAsInt();
        int max = Arrays.stream(arr).max().getAsInt();
        int range = max - min + 1;

        int[] count = new int[range];
        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]--;
            }
        }
    }
}
3. 并行计数排序

改进点 :利用多线程加速计数统计。
适用场景:超大数据集或并行计算环境。

java 复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ParallelCountingSort {
    void sort(int[] arr) {
        if (arr.length == 0) return;

        int min = Arrays.stream(arr).min().getAsInt();
        int max = Arrays.stream(arr).max().getAsInt();
        int range = max - min + 1;
        int[] count = new int[range];

        // 并行统计
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new CountTask(arr, count, 0, arr.length - 1, min));

        // 累加统计结果
        for (int i = 1; i < count.length; i++) {
            count[i] += count[i - 1];
        }

        int[] output = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            int num = arr[i];
            output[count[num - min] - 1] = num;
            count[num - min]--;
        }

        System.arraycopy(output, 0, arr, 0, arr.length);
    }

    // 并行统计任务
    private static class CountTask extends RecursiveAction {
        private final int[] arr;
        private final int[] count;
        private final int start;
        private final int end;
        private final int min;

        CountTask(int[] arr, int[] count, int start, int end, int min) {
            this.arr = arr;
            this.count = count;
            this.start = start;
            this.end = end;
            this.min = min;
        }

        @Override
        protected void compute() {
            if (end - start < 1000) { // 小区间直接统计
                for (int i = start; i <= end; i++) {
                    count[arr[i] - min]++;
                }
            } else {
                int mid = (start + end) / 2;
                invokeAll(new CountTask(arr, count, start, mid, min),
                        new CountTask(arr, count, mid + 1, end, min));
            }
        }
    }
}

三、变体对比表格

变体名称 时间复杂度 空间复杂度 稳定性 主要特点 适用场景
基础计数排序 O(n + k) O(k) 稳定 简单易实现,适用于小范围数据 值域较小且无负数的场景
支持负数的计数排序 O(n + k) O(k) 稳定 扩展支持负数,需计算最小值 数据包含负值的场景
原地计数排序 O(n + k) O(k) 稳定 减少输出数组的使用,直接修改原数组 内存受限但时间允许的场景
并行计数排序 O(n/k + k)(并行) O(k) 稳定 利用多线程加速统计步骤 超大数据集或高性能计算环境

四、关键选择原则

  1. 基础场景:优先使用基础计数排序,因其简单且适用于小范围数据。
  2. 负数支持:当数据包含负值时,需使用支持负数的变体。
  3. 内存限制:原地排序适合内存紧张但允许额外统计数组的场景。
  4. 性能优化:并行计数排序适用于超大数据集或并行计算环境。
  5. 稳定性需求:所有变体均稳定,适用于需保持元素相对顺序的场景(如排序带键值的记录)。

通过选择合适的变体,可在特定场景下优化性能或扩展适用性。例如,并行计数排序在大数据集上显著提升速度,而支持负数的变体扩展了算法的通用性。

相关推荐
Doro再努力9 分钟前
【数据结构08】队列实现及练习
数据结构·算法
SuperherRo1 小时前
JAVA攻防-Shiro专题&断点调试&有key利用链&URL&CC&CB&原生反序列化&加密逻辑
java·shiro·反序列化·有key·利用链·原生反序列化·加密逻辑
桦说编程1 小时前
简单方法实现子任务耗时统计
java·后端·监控
爱笑的眼睛111 小时前
超越可视化:降维算法组件的深度解析与工程实践
java·人工智能·python·ai
盖世英雄酱581361 小时前
物品超领取损失1万事故复盘(一)
java·后端
CryptoRzz1 小时前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链
清铎2 小时前
leetcode_day12_滑动窗口_《绝境求生》
python·算法·leetcode·动态规划
linweidong2 小时前
嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
stm32·单片机·算法
net3m332 小时前
单片机屏幕多级菜单系统之当前屏幕号+屏幕菜单当前深度 机制
c语言·c++·算法