C++ 计数排序可视化演示

原始数组 (A)
等待生成数据
统计 / 累加数组 (C)
等待开始
结果数组 (B)
等待排序
默认 / 未处理
当前扫描 / 处理中
已处理 / 已放置
计数元素 (边框)
计数更新中 (边框)
状态信息将显示在这里
参数设置
范围: 5-40
范围: 5-20

请输入非负整数数组(用逗号或空格分隔),并指定最大值 k。

动画控制
1000ms
(毫秒)
(空格键)
(空格键)
(→)

计数排序:原理与应用 (非负整数版)

计数排序是一种高效的 非比较型 整数排序算法。它不通过比较元素来排序,而是利用元素值的频率信息。此实现主要针对 **非负整数**。

核心思想

统计每个不同非负整数出现的次数,然后利用这些次数信息直接计算出每个元素在排序后输出数组中的最终位置。

基本步骤 (稳定版)

  1. 确定范围: 找到待排序数组 `A` 中的最大值 `k`。确保所有数据均为非负。
  2. 初始化计数数组: 创建大小为 `k + 1` 的计数数组 `C`,所有元素初始化为 0。C[i] 用于存储值 i 出现的次数。
  3. 统计频率: 遍历输入数组 `A`,对每个元素 x,执行 C[x] = C[x] + 1
  4. 计算累加和: 修改计数数组 `C`,使得 C[i] 存储的是小于或等于 i 的元素的总个数。通过累加实现:C[i] = C[i] + C[i-1] (从 i=1 开始)。这一步确定了每个值对应的“最后一个位置”的索引(加1后)。
  5. 创建输出数组: 创建与输入数组 `A` 大小相同的输出数组 `B`。
  6. 放置元素 (反向遍历): 从 `A` 的最后一个元素开始向前遍历。对于当前元素 x
    • 计算其在 B 中的位置:position = C[x] - 1
    • x 放入 B[position]
    • C[x] 减 1,为下一个相同值的元素空出前一个位置。
    反向遍历 是保证算法 稳定性 (相同元素的相对顺序不变) 的关键。
  7. 复制结果: (可选) 将排序好的 B 复制回 A

关键优势: 时间复杂度为 线性 O(n + k),其中 n 是数组大小,k 是数据范围 (最大值)。当 k 相对于 n 不太大时,效率极高。

适用场景与局限性

  • 适用:非负整数 进行排序,特别是范围已知且不巨大的情况。
  • 局限:
    • 直接用于负数或浮点数需要额外技巧(如偏移量或转换)。
    • 空间复杂度较高:需要 O(n) 的输出数组和 O(k) 的计数数组。当 k 极大时,空间开销成为瓶颈。
    • 通常不是原地排序算法。

C++ 代码示例 (稳定版, 处理非负整数)

{/* 优化:已转义 < 和 > */}

#include <vector>       // 用于 std::vector
#include <iostream>    // 用于 std::cout, std::cerr, std::endl
#include <algorithm>   // 用于 std::max_element
#include <stdexcept>   // 用于 std::out_of_range (可选的异常处理)

// 使用 std 命名空间,避免重复写 std::
using namespace std;

/**
 * @brief 使用计数排序对非负整数向量进行稳定排序。
 *
 * @param arr 待排序的整数向量 (会被原地修改)。函数假设所有元素都是非负的。
 *            如果包含负数,此实现将可能出错或行为未定义。
 */
void countingSort(vector<int>& arr) {
    // 0. 处理边界情况:空数组或只有一个元素
    if (arr.size() <= 1) {
        // cout << "数组为空或只有一个元素,无需排序。" << endl;
        return; // 直接返回
    }

    // 1. 确定范围:找到数组中的最大值 (k)
    // 使用 *max_element 找到指向最大元素的迭代器,然后解引用得到最大值
    int maxVal = 0; // 初始化为 0,防止空数组或全 0 数组出错
    bool firstElement = true;
    for (int x : arr) {
        if (x < 0) {
            cerr << "错误:计数排序当前实现不支持负数。发现值: " << x << endl;
            // 可以选择抛出异常或提前返回
            // throw std::invalid_argument("计数排序不支持负数");
            return; // 或者直接返回,不进行排序
        }
        if (firstElement) {
            maxVal = x;
            firstElement = false;
        } else if (x > maxVal) {
            maxVal = x;
        }
    }
     // 如果数组非空,现在 maxVal 是真正的最大值
     // 或者使用: if (!arr.empty()) maxVal = *max_element(arr.begin(), arr.end()); else return;


    // 2. 初始化计数数组 C
    // 大小为 k+1 (即 maxVal+1),覆盖从 0 到 maxVal 的所有值
    // count[i] 将存储元素 i 出现的次数
    vector<int> count(maxVal + 1, 0); // 使用 vector 并初始化为 0

    // 3. 统计频率
    // 遍历输入数组 arr,统计每个元素出现的次数
    for (int x : arr) {
        // x 已经检查过是非负数且 x <= maxVal
        count[x]++; // 对应元素的计数加 1
    }

    // 4. 计算累加和 (前缀和)
    // 修改计数数组,使 count[i] 存储小于或等于 i 的元素的总个数
    // 从索引 1 开始累加
    for (int i = 1; i <= maxVal; ++i) {
        count[i] += count[i - 1];
    }
    // 现在 count[i] 表示值 i 的元素应该放在输出数组的最后一个位置的索引 + 1

    // 5. 创建临时输出数组 B
    // 大小与输入数组 arr 相同
    vector<int> output(arr.size());

    // 6. 放置元素 (反向遍历以保证稳定性)
    // 从输入数组的最后一个元素开始遍历
    for (int i = arr.size() - 1; i >= 0; --i) {
        int value = arr[i]; // 当前元素的值

        // 计算元素 value 在输出数组中的目标位置 (0-based index)
        // count[value] 是 <= value 的元素个数 (即最后一个 value 的位置 + 1)
        // 所以目标索引是 count[value] - 1
        int position = count[value] - 1;

        // 放置前检查位置有效性 (虽然理论上应该有效,增加健壮性)
        if (position < 0 || position >= output.size()) {
             cerr << "错误: 计算出的放置位置无效 (" << position << ") for value " << value << endl;
             // 可能需要错误处理或跳过
             continue; // 跳过这个元素
        }

        // 将元素放置到输出数组的计算出的位置
        output[position] = value;

        // 将 count[value] 减 1,为下一个具有相同值的元素准备前一个位置
        count[value]--;
    }

    // 7. 复制结果
    // 将排好序的输出数组 B 复制回原数组 arr
    arr = output; // 使用 vector 的赋值操作符,简洁高效
    // 或者使用 std::copy: copy(output.begin(), output.end(), arr.begin());
}

// --- 示例用法 ---
int main() {
    // 创建一个示例向量 (只包含非负整数)
    vector<int> data = {4, 2, 2, 8, 3, 3, 1, 0, 9, 5}; // 包含 0 和重复元素

    cout << "原始数组: ";
    for(int x : data) {
        cout << x << " ";
    }
    cout << endl;

    // 调用计数排序函数
    countingSort(data);

    cout << "排序后数组: ";
    for(int x : data) {
        cout << x << " ";
    }
    cout << endl << endl;

    // 测试空数组
    vector<int> emptyData = {};
    cout << "测试空数组..." << endl;
    countingSort(emptyData); // 应能安全处理
    cout << "排序后空数组: (应为空)" << endl;
    for(int x : emptyData) cout << x << " "; cout << endl << endl;


     // 测试只包含一个元素的数组
     vector<int> singleData = {5};
     cout << "测试单元素数组..." << endl;
     countingSort(singleData);
     cout << "排序后单元素数组: ";
     for(int x : singleData) cout << x << " ";
     cout << endl;

    return 0;
}

伪代码 (非负整数版)

COUNTING-SORT(A, k)  // A 是输入数组 (元素范围 0 到 k, 均为非负)
  let C[0..k] be a new array initialized to 0       // 计数数组 C
  let B[0..length[A]-1] be a new array for output // 输出数组 B

  // 步骤 1: 统计频率 (C[i] = A 中等于 i 的元素个数)
  for j = 0 to length[A] - 1
    C[A[j]] = C[A[j]] + 1

  // 步骤 2: 计算累加和 (C[i] = A 中小于或等于 i 的元素个数)
  for i = 1 to k
    C[i] = C[i] + C[i-1]

  // 步骤 3: 放置元素到输出数组 B (反向遍历保证稳定性)
  for j = length[A] - 1 downto 0
    value = A[j]                  // 获取当前元素值
    position = C[value] - 1       // 计算其在 B 中的 0-based 索引位置
    B[position] = value           // 将元素放入 B
    C[value] = C[value] - 1       // 更新 C[value],为下一个相同元素准备位置

  // 步骤 4: (可选) 将排序后的 B 复制回 A
  // for j = 0 to length[A] - 1
  //   A[j] = B[j]

  return B // 返回排序后的数组