ZetCode

Java 计数排序算法

最后修改时间:2025 年 4 月 16 日

排序算法简介

算法是解决问题或执行计算的逐步过程。排序算法按特定顺序排列元素,通常是数字或按字典顺序排列。高效排序对于优化其他算法至关重要。

常见的排序算法包括

计数排序概述

计数排序是一种基于非比较的排序算法,它通过计算每个元素出现的次数来进行排序。它的时间复杂度为 O(n + k),其中 n 是元素的数量,k 是输入的范围。

当输入数据的范围 (k) 不明显大于元素数量 (n) 时,计数排序是高效的。它通常用作诸如基数排序之类的其他算法中的子程序。

计数排序实现

这是一个用于整数数组的计数排序的基本实现

CountingSort.java
package com.zetcode;

public class CountingSort {

    public static void countingSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // Find the maximum value to determine the range
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }

        // Initialize count array
        int[] count = new int[max + 1];

        // Store count of each element
        for (int num : arr) {
            count[num]++;
        }

        // Modify count array to store cumulative counts
        for (int i = 1; i <= max; i++) {
            count[i] += count[i - 1];
        }

        // Build the output array
        int[] output = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[arr[i]] - 1] = arr[i];
            count[arr[i]]--;
        }

        // Copy the output array to original array
        System.arraycopy(output, 0, arr, 0, arr.length);
    }

    public static void main(String[] args) {

        int[] arr = {4, 2, 2, 8, 3, 3, 1};
        System.out.println("Original array: " + Arrays.toString(arr));
        
        countingSort(arr);
        System.out.println("Sorted array: " + Arrays.toString(arr));
    }
}

此实现首先找到最大值以确定范围。然后,它计算每个元素出现的次数,计算累积计数,最后构建已排序的数组。

文本数据的计数排序

计数排序也可以适用于对字符串或字符进行排序。 这是一个对字符串中的字符进行排序的示例

CharCountingSort.java
package com.zetcode;

public class CharCountingSort {

    public static String countingSort(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }

        char[] arr = input.toCharArray();
        int n = arr.length;

        // The number of possible ASCII characters
        int range = 256;
        int[] count = new int[range];

        // Count occurrences of each character
        for (char c : arr) {
            count[c]++;
        }

        // Build the output string
        int index = 0;
        for (int i = 0; i < range; i++) {
            while (count[i] > 0) {
                arr[index++] = (char) i;
                count[i]--;
            }
        }

        return new String(arr);
    }

    public static void main(String[] args) {

        String text = "counting sort example";
        System.out.println("Original: " + text);
        System.out.println("Sorted: " + countingSort(text));
    }
}

此版本计算 ASCII 字符(范围 0-255),并按排序顺序重建字符串。请注意,它基于 ASCII 值进行排序,因此大写字母将出现在小写字母之前。

降序计数排序

要按降序排序,我们可以修改计数排序算法以从最高到最低处理计数

DescendingCountingSort.java
package com.zetcode;

public class DescendingCountingSort {

    public static void countingSortDescending(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];
        int[] output = new int[arr.length];

        // Count occurrences
        for (int num : arr) {
            count[num - min]++;
        }

        // Modify count for descending order
        for (int i = count.length - 2; i >= 0; i--) {
            count[i] += count[i + 1];
        }

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

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

    public static void main(String[] args) {

        int[] arr = {4, 2, 2, 8, 3, 3, 1};
        System.out.println("Original: " + Arrays.toString(arr));
        
        countingSortDescending(arr);
        System.out.println("Sorted (descending): " + Arrays.toString(arr));
    }
}

此实现计算从最小值到最大值的范围。然后,它从最高到最低处理计数以实现降序排列。

计数排序与快速排序基准测试

让我们将计数排序与快速排序进行比较,以了解它们的性能特征。我们将使用不同的输入大小和范围进行测试。

SortBenchmark.java
package com.zetcode;

import java.util.Arrays;
import java.util.Random;

public class SortBenchmark {

    public static void countingSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // Find the maximum value to determine the range
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }

        // Initialize count array
        int[] count = new int[max + 1];

        // Store count of each element
        for (int num : arr) {
            count[num]++;
        }

        // Modify count array to store cumulative counts
        for (int i = 1; i <= max; i++) {
            count[i] += count[i - 1];
        }

        // Build the output array
        int[] output = new int[arr.length];
        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[arr[i]] - 1] = arr[i];
            count[arr[i]]--;
        }

        // Copy the output array to original array
        System.arraycopy(output, 0, arr, 0, arr.length);
    }

    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {

        int[] sizes = {1000, 10000, 100000};
        int[] ranges = {100, 1000, 10000};

        for (int size : sizes) {
            for (int range : ranges) {
                System.out.printf("\nBenchmark - Size: %,d, 
                    Range: %,d\n", size, range);
                
                int[] arr1 = generateRandomArray(size, range);
                int[] arr2 = Arrays.copyOf(arr1, arr1.length);
                
                // Counting Sort
                long start = System.nanoTime();
                countingSort(arr1);
                long end = System.nanoTime();
                System.out.printf("Counting Sort: %.3f ms\n", (end - start) / 1e6);
                
                // Quick Sort
                start = System.nanoTime();
                quickSort(arr2, 0, arr2.length - 1);
                end = System.nanoTime();
                System.out.printf("Quick Sort:    %.3f ms\n", (end - start) / 1e6);
            }
        }
    }

    private static int[] generateRandomArray(int size, int range) {
        Random random = new Random();
        int[] arr = new int[size];
        for (int i = 0; i < size; i++) {
            arr[i] = random.nextInt(range);
        }
        return arr;
    }
}

基准测试表明,当范围 (k) 相对于输入大小 (n) 较小时,计数排序表现更好。 对于更大的范围或范围未知的情况,快速排序通常表现更好。

何时使用计数排序

以下情况下,计数排序是理想的:

以下情况下,避免使用计数排序:

来源

维基百科上的计数排序

在本教程中,我们介绍了 Java 中的计数排序算法,包括用于按升序和降序排列的数字和文本数据的实现。 我们还将它的性能与快速排序进行了比较。

作者

我叫 Jan Bodnar,是一位经验丰富的程序员。 我从 2007 年开始撰写编程文章,至今已撰写了 1,400 多篇文章和八本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程