基数排序属于"分配式排序",又称"桶子法",它是一种借助多关键字的思想对单逻辑关键字进行排序的算法。

基数排序有两种方法:

1.最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
2.最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
因为LSD更符合大家的思维方式,下面我以LSD为例讲解:

假设初始序列为:Array{3,1,13,24,36,23,21,44,25,19}

任何一个整数各个位上的基数都可以0~9来表示,我们不妨把0~9视为10个桶。

我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:Array[0] = 3,个位数上是3,将这个数存入编号为3的桶中。

[0]   
[1]1 21
[2]   
[3]31323
[4]2444 
[5]25  
[6]36  
[7]   
[8]   
[9]19  

分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来。

这时,得到的序列就是个位数上呈递增趋势的序列。 

按照个位数排序: {1, 21, 3, 13, 23, 24, 44, 25, 36, 19}。

接下来,对十位数也按照这种方法进行排序。

[0]13  
[1]1319  
[2]21232425
[3]36   
[4]44   
[5]    
[6]    
[7]    
[8]    
[9]    
这是我们从各个桶中将这些数按照从编号0到编号9的顺序依次将所有数取出来。

这是得到序列:{1,3, 13,19,21,23,24,25,36, 44}。



也可以从下图中看出基数排序的过程:








下面是代码实现:

int getMaxBit(int *arr, int length) //辅助函数,求数据的最大位数
{
	int bitNum = 1; //保存最大的位数
	int p = 10;
	for (int i = 0; i < length; ++i)
	{
		while (arr[i] >= p)
		{
			p *= 10;
			++bitNum;
		}
	}
	return bitNum;
}

void RadixSort(int *arr, int length)//基数排序
{
	int bitNum = getMaxBit(arr, length);
	int *tmp = new int[length];
	int *count = new int[10];//计数器
	int i, j, k;
	int radix = 1;
	for (i = 1; i <= bitNum; i++)//进行bitNum次排序
	{
		for (j = 0; j < 10; j++)
			count[j] = 0;//每次分配前清空计数器
		for (j = 0; j < length; j++)
		{
			k = (arr[j] / radix) % 10;//统计每个桶中的记录数
			count[k]++;
		}
		for (j = 1; j < 10; j++)
			count[j] = count[j - 1] + count[j];
		// 这里要从右向左扫描,保证排序稳定性
		for (j = length - 1; j >= 0; j--)//将所有桶中记录依次收集到tmp中
		{
			k = (arr[j] / radix) % 10;
			tmp[count[k] - 1] = arr[j];
			count[k]--;
		}
		for (j = 0; j < length; j++)
			arr[j] = tmp[j];
		radix *= 10;
	}
	delete[] tmp;
	delete[] count;
}
这里写的是最低位优先法,最高位优先法是从最高位开始放入桶中,然后将每个桶中的数再次放入另外的桶中,不断重复,直到每个桶中只有一个元素,可以通过迭代实现。

基数排序的性能

排序类别

排序方法

时间复杂度

空间复杂度

稳定性

复杂性

平均情况

最坏情况

最好情况

基数排序

基数排序

O(d(n+r))

O(d(n+r))

O(d(n+r))

O(n+r)

稳定

较复杂



这里我以一千万个元素的随机数组进行测试,快排用了2.587950s,而基数排序用了3.383550s,基数排序比快排慢。但是基数排序的时间复杂度明显比快排低,这是什么原因呢?当然,随着数组元素的增加,基数排序时间呈线性增长,比如数组元素个数达到亿这个数量级的时候,基数排序慢慢就快于快速排序了。针对基数排序,还可以做很多方面的优化,比如我们可以把基数从10变为1000,将上面代码的10换为1000,相应的改变循环次数,我们会发现算法效率提高了很多。还是上面随机生成的1000万个元素的数组,只需1.322851s,速度明显提升了很多。基数最好的是选择2的幂,计算机是以二进制存储数据的,所以当采用10的幂作为基数时就会出现很多问题。
到这里,基数排序差不多也就介绍完了。算法还有很多可以改进的地方,但最要的是算法的思想,我们明白算法的原理并能够直接在脑中呈现它的模型,那么无论它怎么变,我们都可以从更高的层次把握它。

如果有什么问题,乐意和大家讨论。

另外,其他排序算法,可以参考我的另外一篇博客

“排序算法及并向析”:http://blog.csdn.net/secyb/article/details/51319391。






Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐