计数排序、基数排序和桶排序

本文深入探讨了三种非比较排序算法:计数排序、桶排序和基数排序。计数排序基于统计词频,适合范围确定的整数排序;桶排序将数据分到桶内再排序,基数排序则按数字的每一位进行排序。文章详细阐述了每种排序算法的原理、步骤和优化方法,并分析了它们的时间复杂度和稳定性。通过对算法的理解,可以更好地选择适合特定场景的排序方法。
摘要由CSDN通过智能技术生成

计数排序、基数排序和桶排序


计数排序

不同于基于比较和交换的排序,计数排序是基于统计词频的排序,计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中,这就要求了待排元素必须是有确定范围的整数。


计数排序的过程如下📺

countSort

计数排序步骤:

  • 确定数组元素的范围,即为 最大值和最小值
  • 开辟对应范围长度的数组 countArr[],遍历统计待排元素设为 num
  • 对于每次出现的 num,将 countArr[num]++
  • 此时按序按次输出 countArr[],即排好序

桶排序

桶排序如其名,工作原理是 将待排元素分到有限数量的桶里(桶的划分是有序的),在每个桶内进行排序,最后按顺序输出得到有序序列。


示例如下:

bucketSort

步骤如下:

  • 设置一个定量的数组当作空桶
  • 遍历输入数据,并且把数据一个一个放到对应的桶里去
  • 对每个不是空的桶进行排序
  • 从不是空的桶里把排好序的数据拼接起来

基数排序 🔥🔥🔥

概述与演示

基数排序是基于 关键字各位 的大小来进行排序的,有两种方法:

  • 最高位优先 MSD :按关键字权重 递减 依次 逐层 划分成若干更小的子序列,最后将所有子序列依次连接成一个有序序列
  • 最低位优先 LSD :按关键字权重 递增 依次进行排序

基数排序演示📺 LSD

radixSort


传统的算法步骤

  • 设待排元素 基数r ,例如十进制数 1024 基数为 10,则分配 r 个桶子

  • LSD 为例,假设待排序列为:73, 22, 93, 43, 55, 14, 28, 65, 39, 81

  • 基数为10,桶号为 0~9,,从最低位优先,首先根据 个位 数值,分配到对应桶子中:

    0123456789
    812273、93、431455、652839
  • 将桶子中数值重新组成序列: 81, 22, 73, 93, 43, 14, 55, 65, 28, 39

  • 十位 数值,分配到对应桶子中:

    0123456789
    1422、2839435565738193
  • 将桶中数值重新组成序列:14, 22, 28, 39, 43, 55, 65, 73, 81, 93,序列已有序


传统步骤算法分析

  • 空间复杂度:使用额外辅助存储空间为 radix,且可重复使用,空间复杂度 O(N)
  • 时间复杂度:由步骤可知,排序共需进行 digist (即最大数字的 位数) 趟分配到桶中 和 从桶中收集再组成,分配需要 O(N),收集需要 O(radix),所以总时间复杂度为:O(digist * (N + radix)),且时间复杂度稳定
  • 稳定性:✔️ 按位分配过程是有序进行的,收集过程同样有序,这决定了整体排序是稳定的

优化的算法步骤


还是以 LSD , 待排序列为:🔰 73, 22, 93, 43, 55, 14, 28, 65, 39, 81 🔰 为例:


  • 假设待排序列最大值有 digit 位,例中 93 有 2 位,那么 digist 决定了 分配和收集(入桶和出桶)的 次数

  • 基数 radix 依然是 10,表示 十进制

  • 先对待排元素进行 计数排序,令计数数组为 count[],则在第一轮(个位)count[]如下:

    0123456789
    0113120011

    表示含义:在当前位 d 位(此时为个位,d = 1)值是 count[j] 的元素有多少个

    比如:个位是 3 的数字有 3 个 73、93、43;个位是 5 的数字有 2 个 55、65

  • count[] 做如下累加 count[j] += count[j - 1] 变形:

    0123456789
    01256888910

    表示含义:在当前位 d 位(此时为个位,d = 1)值 小于或等于 <= count[j] 的元素有多少个

    比如:个位值小于等于 3 的数字有 5个 81、22、73、93、43

    注意💥 :先把 计数排序统计词频得到 count[] 操作 当作是 分配操作

  • 构建与待排序列等规模的辅助数组 bucket[], 此时 从右往左 遍历待排序列:

    • 倒数第一个数 81,个位为 1,count[1] 只有 1 个,按个位比较是最小的,若 收集 的话 ,应该排在最前面,即 buctet[1 - 1] = 81,然后再 count[1] --(因为相当于已经收集了一个元素)
    • 倒数第二个数 39,个位为 9,count[9] 有 10 个,说明 39 按个位比较排在第 10 位,若收集的话,应该是 bucket[10 - 1] = 39,然后再 count[9] --
    • 倒数第三个数 65,个位为 5,count[5] 有 8 个,说明 65 按个位比较排在 第 8 位,若收集的话,应该是 bucket[8 - 1] = 65,然后再 count[5] --
    • ………………

  • 上步操作执行完,得到的 bucket[] 已经是按照个位排序是有序的:81, 22, 73, 93, 43, 14, 55, 65, 28, 39

  • bucket[]元素覆盖到待排序列, 再对 十位 d = 2 进行上述操作,得到的 bucket[] 就是最终结果了


步骤分析

通过以上优化的步骤,我们可以得知,通过基数排序得到词频 count[],和从右往左遍历保证了 先入桶的元素先出桶,即保证了分配和收集的有序性,count[j]++ 可作为 入桶操作,count[j]-- 可作为出桶操作

优化基数排序算法实现代码
// 求数组中最大元素的位数
int maxBits(int arr[],int size) {
	int max = *max_element(arr, arr + size);
	int bits = 0;
	while (max != 0) {
		bits++;
		max /= 10;
	}
	return bits;
}
// 求数字num的某位d上的值,超出最大位数返回0
int getDigist(int num, int d) {
	return (num / (int)pow(10, d - 1)) % 10;
}

// 基数排序 !!!
void radixSort(int arr[], int size) {
	
	int *bucket = new int[size];	// 辅助数组,暂存每轮排完序的序列
	int digist = maxBits(arr, size);
	int i = 0, j = 0;
	for (int d = 1; d <= digist; d++) {	//共有digist轮分配和收集
		int count[10]{ 0 };	// count[10] 计数排序数组 0~9,必须保证每次入桶前 count[]计数是0
		// 分配操作1--计数排序
		for (i = 0; i< size; i++) {
			j = getDigist(arr[i], d);	//令 j 为 arr[i] 的第 d 位的值,取值0~9
			count[j]++;
		}
		//分配操作2--累加前缀
		for (i = 1; i < 10; i++) {
			count[i] += count[i - 1];
		}
		// 收集操作,出桶
		for (i = size - 1; i >= 0; i--) {
			j = getDigist(arr[i], d);
			bucket[count[j] - 1] = arr[i];	// 存储到bucket[]中
			count[j]--;						// 出桶
		}
		//覆盖
		for (i = 0; i < size; i++) {
			arr[i] = bucket[i];
		}
	}
	delete[] bucket;
}

所有非基于比较的排序,都要根据数据状况来排序,所以应用场景比较狭隘

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值