计数排序(C语言实现)

计数排序详解:算法、步骤与代码实现

算法思想

计数排序是一种非比较排序,又称为鸽巢原理,是对哈希直接定址法的变形应用。

操作步骤

  1. 统计相同元素出现次数;
  2. 根据统计的结果将序列回收到原来的序列中。
    在这里插入图片描述

计数排序的特性总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,range))
  3. 空间复杂度:O(range)
  4. 稳定性:稳定

代码实现

#include<stdio.h>
#include<stdlib.h>

//时间复杂度 O(N+range)
//空间复杂度 O(range)
//适合范围集中的数据,只适合整型
void countSort(int* a, int n)//计数排序 -- 非比较排序
{
   
   
	int maxArr = a[0], minArr = a[0];//1.求最大值和最小值
	for (int i 
<think>我们收到了用户关于计数排序的C语言实现的需求。用户提供了三个引用,都是关于计数排序的C语言实现。我们需要从中提取关键信息,为用户提供一个清晰、准确的计数排序实现,并解释其工作原理和特性。 计数排序是一种非比较排序算法,适用于整数排序,特别是当数据的范围(最大值与最小值之差)不大时,效率很高。计数排序的核心思想是统计每个元素出现的次数,然后根据统计结果将元素放回原数组。 根据引用[2]和引用[3],计数排序实现步骤如下: 1. 找出待排序数组中的最大值和最小值,确定数据范围。 2. 根据范围创建一个计数数组(大小为max-min+1),并初始化为0。 3. 遍历数组,统计每个元素出现的次数(使用相对位置,即元素值减去最小值作为索引)。 4. 根据计数数组,将元素按顺序写回原数组。 注意:引用[1]中的实现使用了累积计数然后反向填充的方式,这种方式可以保证排序的稳定性。但是引用[2]和[3]的实现是直接根据计数数组按顺序填充,同样可以实现排序,但这样填充会失去稳定性吗?实际上,引用[2]和[3]的实现是非稳定的,因为它们按照计数数组的索引顺序填充,相同元素的顺序取决于它们出现的顺序(但计数数组的索引是单调递增的,所以相同元素会按出现次数连续填充,因此实际上是稳定的,因为相同元素在原始数组中的顺序在填充时被忽略了,但并不会改变相同元素的相对顺序,因为相同元素会被连续放置)。然而,引用[1]的实现通过反向遍历原数组,保证了相同元素在输出数组中的顺序与原始数组相同(即稳定)。但用户提供的引用[2]和[3]的实现并没有反向填充,而是正向填充,并且是从计数数组的最小索引开始填充,因此相同元素的顺序会被保留(因为计数数组的每个位置对应一个值,相同值会连续放置,而相同值在原始数组中的先后顺序在计数时并没有记录,但由于我们按照计数数组的顺序进行填充,相同值的元素会连续出现,所以原始数组中相同元素的相对顺序在输出时会被打乱?实际上,这里有一个误区:计数排序的稳定性取决于填充的方式。如果我们在填充输出数组时,从原数组的末尾向前遍历(如引用[1]),那么先出现的相同元素会被放在后面(因为count[array[i]]-1的位置是从后往前分配的),但这样在输出时相同元素的顺序就是倒过来的?实际上,为了保持稳定性,我们通常采用反向遍历原数组的方式。引用[1]的实现是稳定的,而引用[2]和[3]的实现虽然简单,但不是稳定的(因为相同元素的填充顺序是按照计数数组的索引顺序,而不是原始顺序)。但是,如果我们只是要求排序,而不要求稳定性,那么引用[2]和[3]的方法也可以。 然而,用户要求的是计数排序的C语言实现,我们应尽可能提供一种通用且稳定的实现。因此,我们将参考引用[1]的稳定实现,并结合引用[2]和[3]中对数据范围的处理(使用最大值和最小值来节省空间)来实现。 具体步骤: 1. 遍历数组,找出最大值max和最小值min,计算范围range = max - min + 1。 2. 创建计数数组count,大小为range,并初始化为0。 3. 遍历原数组,统计每个元素出现的次数(使用元素值减去min作为索引)。 4. 对计数数组进行累积计数(从下标1开始,每个位置等于前一个位置的值加上当前位置的值)。这样count[i]就表示小于等于i+min的元素个数。 5. 创建一个临时输出数组output,大小与原数组相同。 6. 反向遍历原数组(为了保持稳定性),将每个元素根据count数组中的位置放入output数组,然后将该位置的计数减1。 7. 将输出数组的数据复制回原数组。 8. 释放临时数组。 但是,用户提供的引用[2]和[3]并没有使用累积计数和反向填充,而是直接根据计数数组中的计数,按顺序将每个元素重复若干次放入原数组。这种方法更简单,而且也是稳定的(因为相同元素在输出时连续出现,且不会改变相同元素的相对顺序?实际上,这种方法没有保留相同元素的原始顺序,因为相同元素在计数数组中只记录次数,并没有记录原始顺序。但是由于我们是从计数数组的下标从小到大(即元素值从小到大)来填充,相同元素会连续出现,但原始数组中相同元素的先后顺序在填充时丢失了,所以这种方法是不稳定的。比如,原始数组中有两个相同的元素5,第一次出现的5和第二次出现的5在输出时无法区分先后,我们只是连续输出两个5,所以原始顺序被打破了。不过,因为相同元素没有区别,所以这种排序结果也是正确的,但严格来说不稳定。然而,在计数排序中,如果我们要求稳定,通常需要使用累积计数和反向填充。 综上所述,我们将提供两种实现方式供用户参考:一种是稳定的(使用累积计数和反向填充),另一种是非稳定的(直接填充)。但通常计数排序实现为稳定的排序算法。 我们选择实现稳定的版本(引用[1]的方式),同时使用引用[2]和[3]中的最大值最小值优化。 同时,我们需要注意计数排序的局限性:只适用于整数,并且当数据范围很大时,空间消耗较大。 下面我们将给出计数排序的C语言实现(稳定版本): </think>### 计数排序的C语言实现 计数排序是一种非比较排序算法,适用于整数排序,特别在数据范围集中时效率很高。其核心思想是通过统计元素的出现次数,直接确定元素在有序序列中的位置[^2]。主要步骤如下: 1. **确定数据范围**:找出数组中的最小值和最大值 2. **创建计数数组**:根据数据范围分配内存空间 3. **统计元素频率**:遍历原始数组进行计数 4. **重构有序序列**:根据计数数组重新排列元素 #### 稳定版本的实现代码(保持原始顺序) ```c #include <stdio.h> #include <stdlib.h> // 计数排序函数(稳定版本) void counting_sort(int arr[], int n) { // 1. 确定数据范围 int min = arr[0], max = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] < min) min = arr[i]; if (arr[i] > max) max = arr[i]; } int range = max - min + 1; // 2. 创建并初始化计数数组 int* count = (int*)calloc(range, sizeof(int)); int* output = (int*)malloc(n * sizeof(int)); // 3. 统计每个元素的出现次数(相对位置) for (int i = 0; i < n; i++) { count[arr[i] - min]++; } // 4. 计算累积计数(关键稳定化步骤) for (int i = 1; i < range; i++) { count[i] += count[i - 1]; } // 5. 构建输出数组(从后向前保证稳定性) for (int i = n - 1; i >= 0; i--) { int pos = arr[i] - min; output[count[pos] - 1] = arr[i]; count[pos]--; } // 6. 复制回原数组 for (int i = 0; i < n; i++) { arr[i] = output[i]; } // 释放内存 free(count); free(output); } // 测试函数 int main() { int arr[] = {4, 2, 2, 8, 3, 3, 1, -2}; int n = sizeof(arr)/sizeof(arr[0]); printf("排序前: "); for (int i = 0; i < n; i++) printf("%d ", arr[i]); counting_sort(arr, n); // 执行排序 printf("\n排序后: "); for (int i = 0; i < n; i++) printf("%d ", arr[i]); return 0; } ``` #### 算法特性分析 | 特性 | 说明 | |--------------|---------------------------------------| | 时间复杂度 | $O(n+k)$ ($k$为数据范围) | | 空间复杂度 | $O(n+k)$ | | 稳定性 | 稳定(保持相同元素原始顺序) | | 最佳场景 | 数据范围小($k \ll n$)的整数排序 | | 局限性 | 仅适用于整数,数据范围大时内存消耗高 | #### 执行流程说明 ```mermaid graph TD A[原始数组] --> B[确定数据范围] B --> C[创建计数数组] C --> D[统计元素频率] D --> E[计算累积计数] E --> F[反向构建输出数组] F --> G[得到有序序列] ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全天

加油,大佬们!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值