计数排序(Counting Sort)
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。
1. 基本思想
对每一个元素x,确定小于x的元素个数,就可以把x直接放到它在有序序列中的位置上。如果有多个元素具有相同的值时,需要适当处理。
2. 排序流程
假设待排序序列A中值的范围[0, k],其中k表示待排序序列中的最大值。
(1)用一个辅助数组count记录各个值在A中出现的次数,比如count[i]表示i在A中的个数;
(2)依次改变count中元素值,使count[i]表示A中小于等于i的元素个数;
(3)从后往前扫描A数组,A中的元素根据count中的信息直接放到辅助数组b中;
(4)把有序序列b复制到a。
3. 算法实现
代码
#include <stdio.h>
#include <stdlib.h>
/* 序列中记录个数 */
#define N 18
/* 序列中最大值,用于计数数组大小 */
#define MAX 9
int array[N]={9,5,2,4,0,8,6,4,3,1,7,4,2,5,7,4,3,2};
/* 打印数组 */
void print_array(int arr[],int len)
{
int i;
for(i=0;i<len;i++)
printf("%2d ",arr[i]);
printf("\n");
}
/* 函数功能:计数排序
* 参数:arr为待排序序列;len为序列长度;max为序列中最大值
* 假设预排序序列为{1,2,3,4,2,3,2,0} len=8 max=4 范围[0,4]
* 1.创建计数数组count[4+1],临时数组tmp[8]
* 2.计数: [0:1][1:1][2:3][3:2][4:1]
* 3.位置:[0:1][1:2][2:5][3:7][4:8]
* 4.反向:从指定位置依次往前输出(计数递减)。例如第一个2放在位置5,第二个2放在为4...
*/
void counting_sort(int arr[],int len,int max)
{
int i;
int *count=(int *)malloc(sizeof(int)*(max+1)); /* 计数数组[0,max] 大小max+1 */
int *tmp=(int *)malloc(sizeof(int)*len); /* 计数完成后输出到临时数组 */
/* 初始化计数值 */
for(i=0;i<=max;i++)
count[i]=0;
/* 计算等于a[i]的记录个数 */
for(i=0;i<len;i++)
count[arr[i]]++;
printf("Count:\t");
print_array(count,max+1);
/* 计算小于等于a[i]的记录个数 */
for(i=1;i<=max;i++)
count[i]+=count[i-1];
printf("Pos:\t");
print_array(count,max+1);
/* 扫描count数组,把各个元素放在有序序列中相应的位置上 */
for(i=len;i>=0;i--)
{
tmp[count[arr[i]]-1]=arr[i]; /* 计数为相对位置,数组从零开始需要减1 */
count[arr[i]]--;
}
/* 将有序序列从临时数组复制到原数组中 */
for(i=0;i<len;i++)
arr[i]=tmp[i];
printf("After:\t");
print_array(arr,len);
}
void main()
{
printf("Before...\n");
printf("Before:\t");
print_array(array,N);
printf("Sorting...\n");
counting_sort(array,N,MAX);
printf("Sorted.\n");
}
结果
Before...
Before: 9 5 2 4 0 8 6 4 3 1 7 4 2 5 7 4 3 2
Sorting...
Count: 1 1 3 2 4 2 1 2 1 1
Pos: 1 2 5 7 11 13 14 16 17 18
After: 0 1 2 2 2 3 3 4 4 4 4 5 5 6 7 7 8 9
Sorted.
4. 算法分析
时间复杂度
从代码来看,计数排序有5个 for 循环,其中三个时间是 len ,两个时间是 max 。所以总时间 T(3len+2max) ,时间复杂度 o(len+max) ,不管是在最坏还是最佳情况下,此时间复杂度不变。
空间复杂度
排序过程中,需要长度为 max 的计数空间和长度为 len 辅助输出空间,这个空间是比较大的,空间复杂度为 O(len+max) 。
稳定性
计数排序是稳定的。
总结
序列输出之所以采用反向填充是因为正向填充会使得相等数据的前后顺序发生改变,使得排序变得不稳定,从而影响以此为基础的基数排序。
计数排序对待排序序列有约束条件:待排序序列 A 中值的范围 [0, max],其中 max 表示待排序序列中的最大值,元素值需是非负数(也可将正负数分开处理实现含负数的排序),max 太大的话会大大降低效率。