基数排序

基本思想

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。

即不需要直接对元素进行相互比较,也不需要将元素相互交换,你需要做的就是对元素进行“分配”。

具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位(或最高位)开始,依次进行一次分配。这样从最低位(或最高位)一直到最高(或最低)位的分配完成以后, 数列就变成一个有序序列。

因此基数排序有两种实现方法。

实现方法

最高位优先(Most Significant Digit First)

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1(即数字最高位)排序分组,同一组中记录,关键码k1(最高位)相等,再对各组按k2(即次高位)排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd(即最低位)对各子组排序后。再将各组连接起来,便得到一个有序序列。

最低位优先(Least Significant Digit First)

最低位优先(Least Significant Digit first)法,简称LSD法:先从kd(即数字最低位)开始排序,再对kd-1(次低位)进行排序,依次重复,直到对k1(最高位)排序后便得到一个有序序列。

LSD代码实现(C++)

//辅助函数:得到给定数字的位数
int getMaxDigit(int x)
{
	int digit = 1;		//默认位数位1位,及0-9
	int radix = 10;		//默认基数位10

	while (x >= radix)
	{
		//大于10说明至少为2位,后面大于100,1000等同理
		digit++;		//位数加一
		radix *= 10;	//进位
	}

	return digit;
}


//基数排序
void radixSort(vector<int>& v)
{
	//先得到数组中元素的最大位数, 即需要排序的次数
	int time = 0;
	for (int x : v)
	{
		time = max(getMaxDigit(x), time);	//getMaxDigit(x)为辅助函数,用来得到x的位数
	}
	cout << "有" << time << "轮排序\n";
	cout << endl;
	//排序过程中需要用到的临时排序空间,这里使用一个临时的10个队列大小的数组
	vector<queue<int>> tmp(10, queue<int>());
	//这里以最低位优先LSD(Least Significant Digit First)为例
	int radix = 1;		//即先从低位(个位)开始排序
	for (int i = 0; i < time; i++)
	{
		//分配操作:对低位排序
		for (int j = 0; j < v.size(); j++)
		{
			//通过v[j] / radix % 10得到当前元素v[j]最低位,并将当前元素存储到对应下标的队列中。例21 / 1 % 10 = 1
			tmp[v[j] / radix % 10].push(v[j]);	
		}
		
		//收集操作:将排序结果放入原数组
		int k = 0;	//重置原数组索引
		for (int j = 0; j < tmp.size(); j++)
		{
			while (!tmp[j].empty())
			{
				//对应队列不为空说明该队列中有排序的元素
				v[k++] = tmp[j].front();
				tmp[j].pop();
			}
		}

		//测试每轮排序的结果
		cout << "******************************************\n";
		cout << "第" << i + 1 << "轮排序\n";
		for (int a : v)
		{
			cout << a << " ";
		}
		cout << endl;
		cout << "******************************************\n";
		cout << endl;

		//修改radix,进位对高位排序
		radix *= 10;
	}
}

排序过程

将数组 {53, 22,31,95,53,14,117} 以LSD法排序,注意黑色加粗的53用来判断算法稳定性。

首先得到数组中最大元素的位数3,因此需要进行三轮排序。同时我们以一个临时的10队列大小的数组(vector<queue>)来进行每轮排序。

第一轮排序:对个位分类
|下标| 对应队列 |
| 0 | null |
| 1 | 31 |
| 2 | 22 |
| 3 | 53 53 |
| 4 | 14 |
| 5 | 95 |
| 6 | null |
| 7 | 117 |
| 8 | null |
| 9 | null |

得到第一轮排序结果[31, 22, 53, 53, 14, 95, 117],可以发现排序过程中并没有比较操作。

第二轮排序:对十位分类
|下标| 对应队列 |
| 0 | null |
| 1 | 14 117 |
| 2 | 22 |
| 3 | 31 |
| 4 | null |
| 5 | 53 53 |
| 6 | null |
| 7 | null |
| 8 | null |
| 9 | 95 |

得到第二轮排序结果[14, 117, 22, 31, 53, 53, 95]

第三轮排序:对百位分类
|下标| 对应队列 |
| 0 | 14 22 31 53 53 95 |
| 1 | 117 |
| 2 | null |
| 3 | null |
| 4 | null |
| 5 | null |
| 6 | null |
| 7 | null |
| 8 | null |
| 9 | null |

得到第三轮排序结果[14, 22, 31, 53, 53, 95, 117],至此排序结束。

时间复杂度

在不考虑队列的push和pop操作开销的情况下,上面代码的时间复杂度位O(n + time*(n + radixn)),radix为关键码的取值范围,在本例中是10,即O(time(n + 10*n)),化简为O(time * n)。

time为最外层for循环的排序轮数,第一个n为取给定数组最大元素位数的时间开销,第二个n 为分配操作的时间开销,radix = 10, 第三个n 为收集操作的时间开销。

空间复杂度

主要的空间开销在于vector<queue> tmp(radix, queue()),radix为关键码的取值范围(本例中为10),第二维度的队列的取值范围为[0, n - 1], 所以空间复杂度为O(radix * n),明显还有优化空间

算法稳定性

排序过程中黑色加粗的53始终位于未加粗的53前面,相对位置没有发生改变,所以基数排序是稳定的排序算法。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值