[排序算法] 11. 基数排序详解及采用一维数组实现优化(分配排序、算法优化、复杂度分析)

本文深入解析基数排序算法,从基本思想到代码实现,包括动态二维数组和一维数组两种实现方式,详细阐述了算法步骤和性能分析,是理解基数排序原理和实践应用的全面指南。

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

1. 基本思想

基数排序(radix sort)可以看作桶排序的扩展,它是一种多关键字排序算法。 如果记录安照多个关键字排序,则依次按照这些关键字进行排序。例如扑克牌排序,扑克牌由数字面直和花色两个关键字组成,可以先按照面值(2, 3,.,. 10, J,Q, K, A)排序,再按照花色排序。如果记录按照一个数值型的关键字排序,可以把该关键字看作是由 d 位组成的多关键字排序,每一位的值取值范围为 [0,r), 其中 r 称为基数。例如,十进制数 268 由 3 位数组成,每一位的取值范围为 [0,10), 十进制数的基数 r 为 10,同样,二进制数的基数为 2,英文字母的基数为 26。 以下以十进制数的基数排序为例。

基数排序与桶排序很类似,主要算法步骤如下:

  1. 求出待排序序列中最大关键字的位数 d,然后从低位到高位进行基数排序
  2. 按个位将关键字依次分配到桶中,然后将每个桶中的数据依次收集起来
  3. 按十位将关键字依次分配到桶中,然后将每个桶中的数据依次收集起来
  4. 依次下去, 直到 d 位处理完毕,得到一个有序的序列

举个例子:假如有 10 个学生的成绩,(68,75,54,70,83,48,80,12,75*,92)
,对该成绩序列进行桶排序。需要进行以下步骤:

  1. 确定关键字
    待排序序列中最大关键字 92 为两位数,只需要两趟基数排序即可
  2. 分配
    首先按照个位数,划分为 10 个桶 (0~9),将学生成绩依次放入桶中,如下图所示:
    在这里插入图片描述
  3. 收集
    将每个桶内的记录依次收集起来,得到一个序列(70,80,12,92,83,54,75,75*,68,48)
  4. 分配
    再按照十位数,划分为10个桶(0~9), 将学生成绩依次放入桶中,如下图所示:
    在这里插入图片描述
  5. 收集
    将每个桶内的记录依次收集起来,得到一个序列(12,48,54,68,70,75,75*,80,83,92)。待排序数据都是两位数,只有两个关键字,排序完毕,得到一个有序序列。

在此着重强调一个概念,依次分配和收集时为什么要“依次”放入和收集?如果不是“依次”会怎么样?

回答这个问题,依旧举个例子:例如对(82,62,65,85)进行基数排序,首先按照个位划分:在这里插入图片描述
收集桶中的数据,(62,82,85,65),再按照十位划分到 6 号和 8 号桶中:
在这里插入图片描述
收集桶中的数据,(65,62,85,82),排序结束并不是一个有序的序列,为什么?

  • 以 62、65 为例,首先按照个位划分,62 在 2 号桶,65 在 5 号桶,2 号桶在 5 号桶的前面,那么相同十位的情况下桶间是有序的,即2 号桶的元素肯定是小于 5 号桶的,那么收集的时候就需要依次进行收集,不能随意更改收集的方式。

如果不是按顺序依次进行分配和收集则无法实现排序结果的正确性。那么如何保证依次分配和收集呢?

  • 一个非常简单的方法就是队列,先进先出,依次进行。因此可以采用队列保持桶中数据的进出顺序,保证排序结果的正确性。也就是说,每一个桶内使用一个队列存储数据,可以使用顺序队列或链式队列。

桶中的多个数据元素可以采用二维数组、链式存储的方式都可以,主要保证“依次”属性即可。下面着重学习一种一位数组的方式进行处理的做法及算法思想:

针对序列:(68,75,54,70,83,48,80,12,75*,92) 进行基数排序,首先按照个位数,,划分 10 个桶(0~9),将学生成绩依次放入桶中,个位是 0 的放入 0 号桶,个位是 2 的放入 2 号桶,等等。如图所示:
在这里插入图片描述
重点:建立辅助数组为–计数器数组,如图所示。个位为0的有两个,count[0]=2
在这里插入图片描述
将计数器数组累加,从下标 1 开始,累加前一项,count[j] += count[j - 1],如图所示:
在这里插入图片描述
累加的效果相当于分配存储区间,例如,count[8]=10, 那么 8 号桶的两个数分配存储空间下标为 9,8; count[5]=8, 那么 5 号桶的两个数分配存储空间下标为7,6,因为下标从 0 开始。如图所示:
在这里插入图片描述
利用 count[] 数组,将桶中的数据收集到辅助数组 temp 中。序列 从后向前 处理,(68,75,54,70,83,48,80,12,75* 92),相当于一种 映射关系

  • 92 在 2 号桶,count[2]=4--count[2]=3, 将 92 存入temp[3],依次处理完毕后有:

在这里插入图片描述
temp[] 数组中的数据,按照十位数,划分为 10 个桶(0~9)
在这里插入图片描述
计数器数组,如图所示。十位为 7 的有 3 个,count[7]=3
在这里插入图片描述
将计数器数组累加,从下标 1 开始,累加前一项,count[j]+=countfj-1], 如图所示。
在这里插入图片描述
利用 count[] 数组,将桶中的数据收集到辅助数组temp 中。序列从后向前处理,(70,80,12,92,83,54,75,75*,68,48)

  • 48 在 4 号桶,count[4]=2--count[4]=1, 将 48 存入temp[1]
    在这里插入图片描述

将排好序的辅助数组 temp[] 放回原数组即可。排序结果如图所示。
在这里插入图片描述

2. 代码实现

2.1 动态二维数组实现

// 基数排序(递增)
int Maxbit(int array[], int size) {		// 求待排序序列最大元素位数
	int maxvalue = array[0], digits = 0;	// 初始化最大元素为array[0],最大位数为0
	for (int i = 1; i < size; i++) {	// 找到序列中最大元素
		if (array[i] > maxvalue)
			maxvalue = array[i];
	}
	while (maxvalue != 0) {		// 分解得到最大元素的位数
		digits++;
		maxvalue /= 10;
	}
	return digits;
}

int Bitnumber(int x, int bit) {		// 求x第bit位上的数字,例如238第2位上的数字为3
	int temp = 1;
	for (int i = 1; i < bit; i++) {
		temp *= 10;
	}
	return (x / temp) % 10;
}

// 基数排序(递增)
void RadixSort(int array[], int size) {
	int i, j, k, bit, maxbit;
	maxbit = Maxbit(array, size);	// 求最大元素位数
	cout << "最大元素位数为:" << maxbit << "位 " << endl;
	int **B = new int *[10];	// 分配二维动态数组
	for (i = 0; i < 10; i++)
		B[i] = new int[size + 1];	// 每个桶都是size+1个空间,其中每个桶的第一个位置即B[0]第0位存放元素个数
	for (i = 0; i < 10; i++)
		B[i][0] = 0;	//统计第i个桶的元素个数
	// 从个位到高位,对不同的位数进行桶排序
	for (bit = 1; bit <= maxbit; bit++) {
		for (j = 0; j < size; j++) {	// 分配 
			int num = Bitnumber(array[j], bit);	// 取array[j]第bit位上的数字
			int index = ++B[num][0];
			B[num][index] = array[j];
		}
		for (i = 0, j = 0; i < 10; i++) {	// 收集
			for (k = 1; k <= B[i][0]; k++)
				array[j++] = B[i][k];
			B[i][0] = 0;	// 收集后元素个数置零
		}
	}
	for (int i = 0; i < 10; i++)
		delete[]B[i];
	delete B;
}

测试数据:int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
在这里插入图片描述

2.2 一维数组实现

// 基数排序(递增)
// 一维数组实现
const int maxn = 1000;
int a[maxn], size;
int maxbit(int array[], int size) { //辅助函数,求数据的最大位数
	int d = 1;//统计最大的位数
	int p = 10;
	for (int i = 0; i < size; ++i) {
		while (array[i] >= p) {
			p *= 10;
			++d;
		}
	}
	return d;
}

// 基数排序(递增)
// 一维数组实现
void radixsort(int array[], int size) { 
	int d = maxbit(array, size); // 求最大位数 
	int *tmp = new int[size]; // 辅助数组 
	int *count = new int[10]; // 计数器
	int i, j, k;
	int radix = 1;
	for (i = 1; i <= d; i++) { // 进行d次排序
		for (j = 0; j < 10; ++j) {
			count[j] = 0; // 每次分配前清空计数器
		}
		for (j = 0; j < size; ++j) {
			k = (array[j] / radix) % 10; // 取出个位数,然后是十位数,... 
			count[k]++;  // 统计每个桶中的记录数
		}
		for (j = 1; j < 10; ++j) {
			count[j] += count[j - 1]; // 将tmp中的位置依次分配给每个桶
		}
		for (j = size - 1; j >= 0; --j) { //将所有桶中记录依次收集到tmp中
			k = (array[j] / radix) % 10;
			tmp[--count[k]] = array[j];
		}
		for (j = 0; j < size; j++) {	// 将临时数组的内容复制到array中
			array[j] = tmp[j];
		}
		cout << "第" << i << "次排序结果:" << endl;
		for (int i = 0; i < size; ++i)
			cout << array[i] << "   ";
		cout << endl;
		radix = radix * 10;
	}
	delete[]tmp;
	delete[]count;
}

3. 性能分析

3.1 链式队列性能分析

时间复杂度

基数排序需要进行 d 趟排序,每一趟排序包含分配和收集两个操作,分配需要 O ( n ) O(n) O(n)时间,收集操作如果使用顺序队列也需要 O ( n ) O(n) O(n)时间,如果使用链式队列则只需要
r 个链队首展相连即可,需要 O ( r ) O(r) O(r)时间,总的时间复杂度为 O ( d ( n + r ) ) O(d(n+r)) O(d(n+r))

空间复杂度

  • 如果使用顺序队列,需要 r 个大小为 n 的队列,空间复杂度为 O ( m ) O(m) O(m)。如果使用链式队列,则需要额外的指针域,空间复杂度为 O ( n + r ) O(n+r) O(n+r)

排序稳定性

  • 稳定,基数排序时按关键字出现的顺序依次进行的

3.2 一维数组实现

时间复杂度

基数排序需要进行 d 趟排序,每一趟排序包含分配和收集两个操作,分配需要 O ( n ) O(n) O(n)时间,收集操作使用一维数组需要 O ( n ) O(n) O(n)时间,总的时间复杂度为 O ( d × n ) O(d{\times}n) O(d×n)

空间复杂度

  • 使用计数数组 count 的大小为基数 r,辅助数组 temp 的大小为 n,空间复杂度为 O ( n + r ) O(n+r) O(n+r)

排序稳定性

  • 稳定,基数排序时按关键字出现的顺序依次进行的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值