Counting Sort(计数排序)详解及其C++实现

Counting Sort(计数排序)详解及其C++实现

计数排序是一种非比较型整数排序算法,对于每一个输入的元素,该算法在辅助数组中计算出该元素的位置。它的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序对于数据范围不是特别大的情况非常有效。

基本原理

计数排序不是基于比较的排序算法,其基本思想是对每一个输入的元素x,确定小于x的元素个数。这样可以直接把每个元素x放到它在输出数组的位置上。为了实现这一点,算法需要一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

算法步骤

Counting Sort是一种非比较型的排序算法,它的时间复杂度可以达到O(n+k),其中n是数据的个数,k是数据的范围。 它的基本原理如下:

  1. 确定数据范围
    • 首先需要确定输入数据的范围,即数据的最大值和最小值。这个范围记为k。
  1. 创建计数数组
    • 创建一个大小为k+1的计数数组count,用来统计每个元素出现的次数。初始化count数组全部为0。
  1. 统计元素出现次数
    • 遍历输入数组,将每个元素的值作为count数组的下标,并将对应位置的值加1,统计每个元素出现的次数。
  1. 累加计数数组
    • 从左到右遍历count数组,使得count[i]的值等于前i个值的和。这样count[i]就存储了小于等于i的元素的个数。
  1. 输出有序数组
    • 反向遍历输入数组,将每个元素插入到输出数组的正确位置上。具体做法是:
      • 找到该元素在count数组中的下标,即该元素的排序位置。
      • 将该元素放在输出数组的对应位置。
      • count数组中对应下标的值减1,为下一个相同元素的插入做准备。

计数排序的优点:

  1. 时间复杂度为O(n+k),其中n是数据个数,k是数据范围,当k相对较小时,Counting Sort的性能优于比较类排序算法。
  2. 空间复杂度为O(n+k),需要额外的计数数组。
  3. 稳定性好,能够保持相同元素的相对顺序。

计数排序的缺点:

  1. 需要预先知道数据范围k,当k太大时,需要大量的内存空间。
  2. 只能用于整数排序,对于浮点数和字符串等数据类型不适用。

不过,我们需要清楚的是:Counting Sort是一种高效且稳定的排序算法,在某些场景下可以取得很好的性能。

计数排序的C++具体实现

下面是计数排序的C++实现,有助于大家理解其每一步的操作:

#include <iostream>
#include <vector>

void countingSort(std::vector<int>& arr) {
    if (arr.empty()) return;

    int max = arr[0], min = arr[0];
    // 1. 找出数组的最大值和最小值
    for (int num : arr) {
        if (num > max) max = num;
        if (num < min) min = num;
    }

    // 2. 根据最大值和最小值确定计数数组的大小
    std::vector<int> count(max - min + 1, 0);

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

    // 4. 根据计数数组,重新排序原数组
    int index = 0;
    for (int i = 0; i < count.size(); i++) {
        while (count[i] > 0) {
            arr[index++] = i + min;
            count[i]--;
        }
    }
}

int main() {
    std::vector<int> data = {4, 2, 2, 8, 3, 3, 1};
    std::cout << "Original array: ";
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    countingSort(data);

    std::cout << "Sorted array: ";
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

计数排序的实际案例(有助于更好的理解算法过程)

假设待排序数组为 [4, 2, 2, 8, 3, 3, 1],元素范围为 [1, 8],那么计数排序的过程如下:

  1. 统计每个元素出现的次数:计数数组为 [1, 2, 2, 2, 1, 0, 0, 1]。
  2. 累加计数:计数数组变为 [1, 3, 5, 7, 8, 8, 8, 9]。
  3. 根据计数数组重新排序:排序后的数组为 [1, 2, 2, 3, 3, 4, 8, 8]。

思考(学而不思则罔)

  1. 计数排序是稳定的排序算法吗?为什么?
    答: 是的,计数排序是一个稳定的排序算法。稳定性意味着相同的元素在排序后会保持它们原始的顺序。计数排序通过计算每个元素之前有多少个元素来实现这一点,确保相同元素的相对顺序保持不变。
  1. 计数排序的时间复杂度是多少?
    答: 计数排序的时间复杂度通常是O(n + k),其中n是数组长度,k是输入数据的范围。当k不是很大且接近n时,计数排序是非常高效的。
  1. 计数排序的空间复杂度是多少?
    答: 计数排序的空间复杂度是O(k),其中k是输入数据的范围。算法需要额外的空间来存储计数数组。
  1. 计数排序适用于哪些类型的输入数据?
    答: 计数排序最适合于数据范围不大的情况,例如整数和某些离散类型的数据。如果数据范围k远大于数组长度n,则计数排序可能不是最佳选择。
  1. 如果数据范围很大,计数排序还合适吗?
    答: 如果数据范围k远大于数组长度n,计数排序可能会消耗大量内存,此时其他排序算法如快速排序或归并排序可能更合适。
  1. 如何处理负数的情况?
    答: 计数排序本身只适用于非负整数。对于包含负数的数组,可以通过调整数值(例如,加上数组中绝对值最大的负数的绝对值),将所有数转换为非负数,排序后再逆向调整。
  1. 计数排序的基本步骤是什么?
    答: 计数排序的基本步骤包括:(1) 计算每个元素的出现次数;(2) 累计计数,以确定每个元素的位置范围;(3) 根据计数数组,将每个元素放到它在输出数组中的正确位置。
  1. 计数排序可以用于排序浮点数吗?
    答: 计数排序通常不用于浮点数,因为它依赖于整数的计数。不过,可以通过一些技巧(如映射函数)将浮点数转换成整数,然后再使用计数排序。
  1. 如何优化计数排序处理大数据范围的情况?
    答: 对于较大的数据范围,可以使用更复杂的算法如基数排序,它使用计数排序作为内部稳定排序方法,分步处理数字的每一位。
  1. 计数排序和桶排序有什么区别?
    答: 计数排序通过计算每个值的出现次数来工作,而桶排序将数组分到多个桶中,每个桶再分别排序。计数排序适用于小范围的整数,桶排序适用于数据分布较均匀的情况,可以处理更广泛类型的数据。

Summary

计数排序适用于当输入的元素是n个0到k之间的整数时。它的时间复杂度是O(n+k),空间复杂度也是O(n+k)。计数排序是一个稳定的排序算法,但由于它依赖输入的最大值和最小值,当数值范围较大或者元素比较分散时,可能不太适用。

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值