基数排序(radix sort)

讨论基数排序之前,先来看桶式排序


桶式排序

桶式排序的原理是:利用待排序序列元素的作为新序列的索引进行插入,完成新序列的建立后,只需对新序列进行遍历即可完成排序(具体遍历过程和新序列元素的值有关)

假设有N个整数的序列,元素的值的范围是0到M-1。现建立一个名为Count的桶序列,长度为M,并初始化为0。于是,Count有M个桶,开始时都是空的。对待排序序列进行遍历,当遍历至原序列元素Ai时,Count[Ai]增1。Count序列建立完毕后,对其进行遍历,遍历输出的不是Count序列元素的值,而是其索引,每个索引输出的次数由其对应元素的值决定,元素值为0则不输出

代码如下

void bucket_sort(int a[], int n, int m){  //n为待排序序列长度,其元素值取范为[0, m-1]
	assert(a != NULL);
	assert(n > 0);
	assert(m > 0);

	int * b = (int *)malloc(m*sizeof(int));
	memset(b, 0, m*sizeof(int));
	for(int i = 0; i < n; i++)  //入桶
		b[a[i]]++;
	for(int i = 0, j = 0; i < n; i++){  //出桶
		while(b[j] == 0) j++;
		a[i] = j;
		b[j]--;
	}
}

桶式排序的局限性在于

  • 待排序序列元素的值只能为整数
  • 若待排序序列元素值的取值范围很大,则Count序列需占用很大空间,而实际上Count序列可能是很稀疏的

基数排序
基数排序可看作多趟桶排序
基数排序适用于多关键字比较,并且和桶排序一样它不需要进行记录关键字之间的比较
基数排序的时间复杂度为O(p*n),空间复杂度为O(r*n) (p为“位数”,r为基)

将数值的每一位看作一个关键字,则同样可对数值应用基数排序。设待排序序列长度为N,取值范围为0到M,选取基数为R,则该序列中所有元素的“位数”(基数R对应的“位”,当基数为10时就是一般意义上的“位”)均不超过P = ceil(lnM/lnR) (对数换底公式)。整个基数排序需要R个高度为N的桶,需要P趟桶排序。每次以元素值的某一“位”为关键字进行桶排序,顺序必须是先从最低“位”开始桶排序,最后对最高“位”桶排序。因此基数排序实际上是反复“入桶”-“出桶”的过程

对于”先低位,后高位“原则,作如下理解:低位的排序结果会影响两个高位相同元素的排序。譬如21和27,第一趟排序时,21所在桶位于27所在桶之前,因此第二趟排序时会先扫描到21。最终结果是,第二趟排序结束后21在27之前

根据上述分析,相比于桶式排序,基数排序有以下特性

  • 桶是一个二维矩阵,大小为R×N
  • 桶式排序的Count序列是一维列表,这是因为同一桶内的元素的值必定相同;而基数排序的同一桶内的元素只是针对该趟对应的“位”相同,元素值不一定相同,因此桶还需要一定的“高度”来记录不同的元素值,因此是二维矩阵
  • 理论上基数R可以任意取值的,但R的选取决定了桶排序的趟数(时间复杂度)和桶的大小(空间复杂度),因此需要一定的平衡。另外,虽然基数为10符合我们的一般思路易于理解,但基数为10时“取位”操作必须通过pow函数和取模运算进行,开销较大。基于上述考虑,我们选取基数为16,故而可使用移位操作进行“取位”

代码如下(基为16)
void radix_sort(int a[], int n, int m){ //max: m
	assert(a != NULL);
	assert(n > 0);
	assert(m > 0);

	int p = (int)ceil(log(m)/log(16));  //p表示以16为基时,数组元素最大值m对应的最高位数
	printf("p = %d\n", p);
	int ** b = (int **)malloc(16*sizeof(int *));  //二维数组b[][]表示桶,桶有一定高度
	for(int i = 0; i < 16; i++)
		b[i] = (int *)malloc(n*sizeof(int));
	int c[16];  //数组c[]记录桶的高度
	int index;

	int mask = 0x0f;
	for(int k = 0; k < p; k++){
		memset(c, 0, 16*sizeof(int));
		for(int j = 0; j < n; j++){  //入桶
			index = (a[j]>>(k*4))&mask;  //index指示当前元素应在哪个桶
			b[index][c[index]] = a[j];
			c[index]++;
		}

		int s = 0;
		for(int i = 0; i < 16; i++){  //出桶
			for(int j = 0; j < c[i]; j++)
				a[s++] = b[i][j];
		}
	}
	for(int i = 0; i < 16; i++)
		free(b[i]);
	free(b);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值