简介
基数排序和桶排序有些像,都不需要比较数据大小,而其他排序算法要。
基数排序分为以下两类
一.最高位优先(Most Significant Digit first)法,简称MSD法:
先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
二.最低位优先(Least Significant Digit first)法,简称LSD法:
先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
例子
先用LSD讲一个例子
假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
第一步
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
适用性及复杂度
复杂度:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。
适用性:LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。
代码实现
一,LSD
inline int maxbit(int data[], int n){//返回data数组中最大的数的长度 int d=1,p(10); //d保存最大的位数 for(int i=0;i<n;i++){ while(data[i]>=p||-data[i]>=p){ p=(p+(p<<2))<<1,d++; } } return d; } inline void lsd_radixsort(int data[], int n){//data为原始数组也是结果数组,n为数组长度 int d=maxbit(data,n),tmp[n];//d为数组中最大的数的长度 int count[20]; //计数器 因为有负数,所以开到20; int radix = 1; for(int i=0;i<d;i++){ //进行d次排序 for(int j=1;j<20;j++) count[j]=0; //每次分配前清空计数器 for(int j=0,k;j<n;j++){//统计每个桶中的记录数 k=(data[j]/radix) % 10+10;//因为有负数所以+10 count[k]++; } for(int j=2;j<20;j++) count[j]=count[j-1]+count[j]; //将tmp中的位置依次分配给每个桶,count[j]为第j个桶的结束位置 for(int j=n-1,k;j>=0; j--){ //将所有桶中记录依次收集到tmp中 k = (data[j] / radix) % 10+10; //统计每个桶中的记录数 tmp[count[k]-1]=data[j]; count[k]--; } memcpy(data,tmp,n*4);//将临时数组的内容复制到data中 radix=(radix+(radix<<2))<<1; } }
二,MSD
int a[] = {1, 1, 10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; inline int getdigit(int x,int d){//开到int最大九位 return ((x/a[d])%10)+10;//返回桶号 } void msd_radixsort(int arr[],int begin,int end,int d){//分别为要排序的数组,开始和结束位置,最大数据长度 const int radix = 21;//因为遍历子桶时需要下一个桶的边界索引,所以多开一个桶 int count[radix], i, j; //初始化 memset(count,0,sizeof(count)); //分配桶存储空间 int *bucket = (int *) malloc((end-begin+1) * sizeof(int)); //统计各桶需要装的元素的个数 for(i = begin;i <= end; ++i){ count[getdigit(arr[i], d)]++; } //求出桶的边界索引,count[i]值为第i个桶的右边界索引+1 for(i = 1; i < radix; ++i) { count[i] = count[i] + count[i-1]; } //这里要从右向左扫描,保证排序稳定性 for(i = end;i >= begin; --i){ j = getdigit(arr[i], d); //求出关键码的第d位的数字, 例如:576的第3位是5 bucket[count[j]-1] = arr[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界索引 --count[j]; //第j个桶放下一个元素的位置(右边界索引+1) } //注意:此时count[i]为第i个桶左边界 //从各个桶中收集数据 for(i = begin, j = 0;i <= end; ++i, ++j){ arr[i] = bucket[j]; } //释放存储空间 free(bucket); //对各个子桶中数据进行再排序 for(i = 1;i < radix-1; i++){ int p1 = begin + count[i]; //第i个子桶的左边界 int p2 = begin + count[i+1]-1; //第i个子桶的右边界 if(p1 < p2 && d > 1){ msd_radixsort(arr, p1, p2, d-1);//对第i个子桶递归调用,进行基数排序,数位降 1 } } }