计数排序假设n个输入元素中的每一个都是介于0到k之间的整数,对每一个输入元素x,确定出小于x的元素的个数,就可以把x直接放到最终输出数组的位置上。例如有17个元素小于x,那么x位于第18个输出位置。而当有几个元素相同时,方案需要修改,不能把他们放在同一个输出位置上。也就是说,计数排序比较适合元素之间没有重复的情况。
就时间效率来看,计数排序几乎是只需要扫描一遍数组就可以将数放在正确的位置上,这比快速排序,堆排序等时间复杂度为nlog(n)快得多(时间复杂度为n),但时间上的缩短将会导致空间上的支出。对于计数排序,可以打个比方,假设输入的数据的最大值为1000,那么新开辟的数组必然要有1000个空间,因为排序的过程可以看成一种过程:假设输入250,那么就将这个数组的第250号位置记为1,而其他的初始化为0(当然,这样的话就不能允许重复的元素出现,所以实际的代码会略有变化,这里可以先这么理解)。所以,即使输入只有3个数,而最大的数是1000的话,那么至少也要开辟1000个数组空间,这样就造成了极大的资源浪费。所以,计数排序的一个缺点就是不适于处理输入值过大的情况(当然,后面使用BitMap也就是位图的话可以缓解这种压力,例如32位机器的话可以使开辟的数组空间减少32倍)。
这里先给出一般情况的计数排序的代码:
#include<stdio.h>
#include<string.h>
//数组的最多输入是MAX个,最大不会超过NewMax
#define Max 10
#define NewMax 100
/*程序中需要两个数组作为参数,数组A为待排序的数组,数组B为排序成功后输出的数组,当然,两者是一样大的
k表示新开辟的数组C的空间*/
void Counting_Sort(int* A,int* B,int k)
{
int i,j;
int C[k];
/*将k全部初始化为0*/
for(i=0;i<k;i++)
C[i]=0;
/*对A进行扫描,在数组C中相应的地方加上1*/
for(j=0;j<Max;j++)
C[A[j]]+=1;
/*一面这一步就是所谓的计算“比他小”的元素有几个,就是通过不断的累加得到*/
for(i=1;i<k;i++)
C[i]=C[i]+C[i-1];
/*这一步向B数组输入元素,需要注意输入的方法
方向是从大到小,原因就在于输入一个后,那么对于”比他大“的元素来说,”比他小“的就少了一个了
从左网友的话,右边所有的元素都要一次减1,而从右向左就只需要把自己减去1就可以了
当然,注意下B的修改方式*/
for(j=Max-1;j>=0;j--)
{
B[C[A[j]]-1]=A[j];
C[A[j]]-=1;
}
}
//主程序进行测试
int main(void)
{
int A[Max]={23,54,99,56,23,5,78,12,3,56};
int B[Max]={};
Counting_Sort(A,B,NewMax);
int i;
for(i=0;i<Max;i++)
printf("%d ",B[i]);
printf("\n");
return 0;
}
当然了,新开辟的空间的大小令人无法忍受,而使用BitMap的话,虽然不能完全不需要新开辟空间,但却能够大大减小新空间的开支。
所谓的BitMap,其实就是一组位的集合,例如32位的int类型可以将它拆分成一个32个0/1的集合,上面的程序中C数组的每一个元素是用int来实现的,那么BitMap中每一个元素是用一个位来实现的,这样可以使空间缩小到原来的32分之1,但由于一个位只能表示0和1,那么以BitMap实现的计数排序就必然不允许有重复的元素出现了。
下面给出以BitMap实现计数排序的代码(位操作代码确实很蛋疼。。。好在我在几乎每一行代码都写了注释了,还看不懂拿块豆腐撞死算了= =):
#include<stdio.h>
#include<string.h>
#define MAX 10//测试数组的最大空间
#define BITSPERWORD 32//32位的位的集合,就是说是以32个元素为一组
#define SHIFT 5//偏移量5,什么是偏移量?将某个数右移5为是不是相当于除以32了呢?
#define MASK 0x1F//除去了32,那么还需要获取余数,就拿这货来做下位运算就行了
#define N 1000000//最大的数这里设置成1000000
int a[ 1 + N/BITSPERWORD ];//需要1000000个元素集合,那么换算成普通的int数组,需要多少个呢?,只需要除个32加1就行了
/*就是把数组里头清零,没什么特别的*/
void clear()
{
memset( a, 0, sizeof(a) );
}
/*下面这个函数是重点,传入一个int值,在位图中特定部分置1
所谓i>>SHIFT,就是确定是在哪一个int里面的位要做修改
那么,i&MASK是神马意思呢?想一下MASK的值是多少?0x1F,化成10进制就是31,一个32位的数,和这玩意儿与一下,
相当于是得到了i除以32后的余数,而让1左移那么多为也可以理解了:不要把它想象成单独的1,而是一个32位的1,前头全是0,
左移那么多位相当于就是在特定的位置置1了
在整个数组中置1的具体实现就是或操作一下就行了*/
void set( int i )
{
a[ i>>SHIFT ] |= ( 1<<( i&MASK ) );
}
/*下面这个函数是把特定的位置清零的,具体分析和上面的差不多,这里就不赘述了*/
void clr( int i )
{
a[ i>>SHIFT ] &= ~(1<<( i&MASK ) );
}
/*测试某一个位是否为1,1<<( i&MASK )就是这个数正常情况下该置一的地方
a[ i>>SHIFT ]是之前修改过的地方,右边的那一位必然为1,只有左边的先前置1了,相与之后才能返回非0值*/
int test( int i )
{
return a[ i>>SHIFT ] & ( 1<<( i&MASK ) );
}
//主函数进行测试
int main(void)
{
int i;
clear();
int A[MAX]={123,2345,767554,23,5,5467,234564,125,54657,99};
for(i=0;i<MAX;i++)
set(A[i]);
for( i = 0; i < N; i++ )
{
if(test(i))
printf("%d ",i);
}
printf("\n");
return 0;
}