桶排序/基数排序 图文理解(java and c/c++实现)

今天做了个后缀数组 , 其中有部分代码需要基排思想所以温故一下,
开始讲解吧;

基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r )m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

操作过程

(1)假设有欲排数据序列如下所示:73 22 93 43 55 14 28 65 39 81首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。分配结果(逻辑想象)如下图所示:
样例来源于网络
(2)分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下仍然无序的数据序列:

81 22 73 93 43 14 55 65 28 39

(3)接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:
样例来源于网络

算法实现

#include<iostream>  
#include<vector>  
#include<random>  
#include<ctime>  
#include<iterator>  
#include<algorithm>  
#include<cmath>
using namespace std;
 
/*
 基数排序
 简化了问题,待排向量为整型,且为三位数
*/
void radixSort(vector<int> & source)
{
	const int BUCKETS = 10;
	vector<vector<int>> buckets(BUCKETS);
 
	//外层循环是根据数字的位数确定的。因为是三位数,所以从2到0
	for (int pos = 0; pos <= 2; ++pos)
	{
		//pos = 0, 表示个位数
		//pos = 1, 表示十位数
		//pos = 2, 表示百位数
		int denominator = static_cast<int>(pow(10, pos)); // 取某一位数的时候需要用的分母
		for (int & tmp : source) // 按数字放入桶中
			buckets[(tmp / denominator) % 10].push_back(tmp);
 
		int index = 0; 
		// 从桶中取出来,放入原来的source序列中,以备下次遍历时使用
		for (auto & theBucket : buckets) 
		{
			for (int & k : theBucket)
				source[index++] = k;
 
			theBucket.clear();//一定要清空桶中数据
		}
	}
}
 
/*输出向量*/
template<typename T>
void printVector(vector<T> & v)
{
	copy(v.cbegin(), v.cend(), ostream_iterator<T>(cout, " "));
	cout << endl;
}
 
int main()
{
	int n,tmp;
	cin>>n;
	vector<int> source;
	for (int i = 0; i < n; i++){
		cin>>tmp;
		source.push_back(tmp);	
	}
	radixSort(source);//sort
	printVector(source);//print
 
	return 0;
}

算法实现(Java)

			// 基数排序的实现,有两种方式,低位优先法,适用于位数较小的数排序 简称LSD
			// 高位优先法,适用于位数较多的情况,简称MSD
			// 这里实现低位优先法
			public static void LSDSort(int a[]) {
				int max = a[0];
				for (int i = 1; i < a.length; i++) {
					if (a[i] > max)
						max = a[i];
				}
				int len = 0; // 存贮最大的数的位数,用来判断需要进行几轮基数排序
				while (max > 0) {
					max = max / 10;
					len++;
				}
 
				for (int times = 0; times < len; times++) // 按位数运行几次,每次都分裂成10份,在顺序链接
				{
					// 以下内容应为每次运行时,分割成0-9 个桶,然后顺序链接
					@SuppressWarnings("unchecked")
					Vector<Integer> num[] = new Vector[10];
					for (int i = 0; i < num.length; i++) {
						num[i] = new Vector<Integer>();
					}
 
					int k = 1; // 用来取出当前的对应的位数的数
					for (int i = 0; i < times; i++) {
						k = k * 10;
					}
 
					for (int i = 0; i < a.length; i++) // 对每一个数进行判断位数
					{
						int x = 0; // 用来表示当前的位数上的数的大小
						x = Math.abs(a[i]) / k; // 使用绝对值,防止受到正负号的影响
						x = x % 10;
						num[x].add(new Integer(a[i]));
					}
					// 将排序的结果顺序连接起来
					int count = 0;
					for (int i = 0; i < num.length; i++) {
						for (int j = 0; j < num[i].size(); j++) {
							a[count++] = num[i].get(j).intValue();
						}
					}
				}
 
				// 判断完绝对值大小顺序后,需要判断正负了
				Vector<Integer> plus = new Vector<Integer>(); // 储存正数
				Vector<Integer> minus = new Vector<Integer>(); // 储存负数
				for (int i = 0; i < a.length; i++) {
					if (a[i] <= 0) {
						minus.add(new Integer(a[i]));
					} else {
						plus.add(new Integer(a[i]));
					}
				}
				int count = 0;
				for (int i = minus.size() - 1; i > 0; i--) // 因为这是按照绝对值大小排列的,所以要倒序
				{
					a[count++] = minus.get(i);
				}
				for (int i = 0; i < plus.size(); i++) {
					a[count++] = plus.get(i);
				}
			}

效率分析(复杂度分析)

性能分析:
在这里插入图片描述

时间复杂度:
在基数排序中,r为基数,d为位数。则基数排序的时间复杂度为O(d(n+r))。
我们可以看出,基数排序的效率和初始序列是否有序没有关联。

空间复杂度:
对于任何位数上的基数进行“装桶”操作时,都需要n+r个临时空间。

算法稳定度
在基数排序过程中,每次都是将当前位数上相同数值的元素统一“装桶”,并不需要交换位置。所以基数排序是稳定的算法。

日拱一卒,功不唐捐 -2020/7/13

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值