计数排序(BitMap实现)

来源:互联网 发布:淘宝店装修流程图解 编辑:程序博客网 时间:2024/06/06 00:43


计数排序假设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//最大的数这里设置成1000000int 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 ){    returna[ 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;}