计数排序(BitMap实现)

计数排序通过计算每个输入元素的频率将其直接放到输出数组的正确位置。适合元素间无重复且值范围不大的场景。位图版本的空间效率更高,通过将每个元素映射到位数组的一位,减小了空间需求。但这种方法不适用于存在重复元素或最大值很大的情况。文章提供了位图实现计数排序的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


计数排序假设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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值