[排序算法] 10. 桶排序及高精度计数器及随机数测试(分配排序)

1. 基本思想

分配排序不需要比较关键字的大小,根据关键字各位上的值,进行若干趟“分配”和“收集”实现排序。

桶排序将待排序序列划分成若干个区间,每个区间形象的看作一个桶,如果桶中的记录多于一个则使用较快的排序方法进行排序,把每个同种的记录收集起来,最终得有序序列。

这个排序方法真的很形象,速度还不慢,举个例子吧:假如有 10 个学生的成绩,(60,75,54,70,83,48,80,12,75*,92)
,对该成绩序列进行桶排序。需要进行以下步骤:

  1. 分配
    学生成绩的范围在:0~100之间,可以划分 10 个桶,0~9,10~19,20~39,...,90~100,将学生成绩依次放入桶中,如下图所示:
    在这里插入图片描述
  2. 利用比较先进的排序算法对每个桶内的数据进行排序
    如果桶内多于一个记录,可以使用较为先进的排序算法进行排序,最好的选择就是快速排序,也可以对每个桶继续使用桶排序。在这用插入排序举个例子,排序后的桶如下图:
    在这里插入图片描述
  3. 收集
    将每个桶内的记录依次收集起来,得到一个有序的序列(12,48,54,68,70,75,75*,80,83,92)

桶排序需要注意的几个问题:

  1. 桶排序的数据最好是均匀分布的。如果有 10 个学生成绩都在 90 分以上,那么 10 个记录都会分配在一个桶内,桶排序就退化成了一般的排序。理想的情况下,当数据均匀分布,桶的数量 m 足够大时,那么每个桶内最多只有一个记录,不需要再进行排序,只需要 O ( n ) O(n) O(n)的时间将所有记录分配到桶中,再用 O ( n ) O(n) O(n)的时间收集起来即可,桶排序的时间复杂度可以达到 0 ( n ) 0(n) 0(n)。 但是这样做空间复杂度较大,是以空间换时间的做法。关于时间复杂度的讨论会在后面性能分析处做详细分析。
  2. 桶排序针对不同的数据选择的划分方法是不同的。例如序列(2, 56,1278, 685,70,7570, 22529, 580, 7, 82), 可以按照位数划分桶,1位数,2位数,3位数,4位数,5位数。
  3. 桶内排序时使用的比较排序算法也有可能不同。可以使用直接插入排序,也可以使用快速排序

2. 代码实现

桶排序比较抽象,采用常用的数字对其进行验证凸显不了它的特点和用途,在此来一道常见的设计性问题:

问题: 要对大小为 [1,1000]n 个整数 a[0,...,n - 1]递增排序,设计相应的桶排序算法。

解答: 假设每个桶的大小为 10,总共需要 1000/10 = 100个桶,一种更有效的方法是求出 a 中的最大元素值 max 和最小元素值 min,设置桶个数 num = (max - min + 1) / 10 + 1。然后对 a 数组从头到尾扫描一遍,把每个 a[i] 放入对应的桶 B[k](k = (a[i] - min + 1) / 10),再对这些痛排序,最后依次输出每个桶里面的元素。对应的算法代码如下:

// 桶排序(递增)
#define BUCKSIZE	20
typedef struct {
	int data[BUCKSIZE];
	int count;		// 桶中元素的个数
} BuckType;			// 桶类型

void BucketSort(int array[], int size) {
	int max, min, num, pos;
	BuckType *pB;
	max = min = array[0];
	for (int i = 1; i < size; ++i) {
		if (array[i] > max) {
			max = array[i];
		}
		else if (array[i] < min) {
			min = array[i];
		}
	}
	num = (max - min + 1) / 10 + 1;		// 求出桶的个数
	pB = (BuckType*)malloc(sizeof(BuckType) * num);
	memset(pB, 0, sizeof(BuckType) * num);

	for (int i = 0; i < size; ++i) {			// 将数组元素分配进桶
		int k = (array[i] - min + 1) / BUCKSIZE;	// 求出array[i]对应的桶号,在此为10个桶
		(pB + k)->data[(pB + k)->count] = array[i];
		(pB + k)->count++;
	}
	pos = 0;
	for (int i = 0; i < num; ++i) {
		QuickSort((pB + i)->data, 0, (pB + i)->count);  // 单个桶快速排序
		for (int j = 0; j < (pB + i)->count; ++j) {
			array[pos++] = (pB + i)->data[j];
		}
	}
}

测试数据:int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
在这里插入图片描述
这组数据缺失没有说服力…桶排序随机硬核250个数字:
在这里插入图片描述
下面是快排250个随机数,发现,我去这桶排序真的无语,和快排时间一样,虽然我内部就是调用的快排啊~~
在这里插入图片描述
至于这个计时器,先卖个关子,在最后会测试各个排序算法的快慢程度会用到,也会给出源码的~~

3. 性能分析

时间复杂度

假设有 n 个元素、m 个桶,如果元素值是平均分配的,则每个桶里面平均有 n / m 个元素。如果对每个桶中的元素采用快速排序,那么桶排序算法的时间复杂度是 O ( n + m ∗ n / m ∗ l o g ( n / m ) ) = O ( n + n l o g n − n l o g m ) 。 O(n+m * n / m*log{(n/m))}=O(n+nlogn-nlogm)。 O(n+mn/mlog(n/m))=O(n+nlognnlogm)m 接近 n 的时候,桶排序的时间复杂度接近 O ( n ) O(n) O(n)

用着快排,比快排还快…

空间复杂度

  • O ( n + m ) O(n + m) O(n+m)

排序稳定性

  • 稳定

来自百度百科:百度百科 桶排序
在这里插入图片描述
这不应该取决于外部排序规则的稳定性吗…在此不要钻牛角尖…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值