注:我测试所用数据都是可以比较大小的类型且无重复元素,且排序按照升序排列。
前言:基数排序可以说是桶排序的一种特殊变型,它的基本思想也是利用了“桶子”.
(桶子在概念上就是类似数组的容器,在这里我用数组来制造桶子)
1、基数排序的算法描述
基数排序总共执行最高位轮,每次从最低位开始比较一直到最高位,元素不足的用0补齐,然后用一个数组记录各个桶子中的元素个数,每次按顺序放入各个桶子中,再按从低到高的顺序,将各个桶子中的元素按照先入先出的顺序依次覆盖原数组,因为当最高位相同时其后面的各个位都是按照从低到高到高的顺序放入桶中的,桶中的元素先入先出,所以当执行完最高位轮后,排序即完成。
2、基数排序的函数实现
先找出数组中的最大元素然后即可得到最高位数是多少,我创建的MaxDigit函数即实现此功能,声明一个10行的二维数组(这就是存元素的桶子),再声明一个10个单元的一维数组记录桶子中的元素个数。共执行最高位次分配,每次先遍历原数组,将每个元素存入其对应位的桶子中去。然后,按从小到大的顺序将桶子中的元素“倒出来”依次覆盖原数组。最外层循环结束则元素排好了。要注意的一点是函数中的数组不能声明太大,当数组长度大于10万后要放在函数外面声明。
void RadixSort(ElementType a[],int length){
int n,times=MaxDigit(a,length),order[10]; /*times 表示最大值有几
位,总循环多少次,order数组用来记录每一行上的元素个数 */
ElementType bucket[10][length]; //临时数组
memset(bucket,0,sizeof(bucket));
int i,j,k,digit=1,lsd; /*digit表示当前比较位,lsd表示当前元素
在比较位上的值,lsd表示从最低位开始比较*/
for(n=0;n<times;n++,digit*=10){
memset(order,0,sizeof(order)); //每次循环都要将order数组置0
for(i=0;i<length;i++){
lsd=(a[i]/digit)%10; //获取当前元素在当前比较位上的值
bucket[lsd][order[lsd]++]=a[i]; /*行是当前比较位上的值从0-9,
order[lsd]的值则是比较位为lsd的元素的个数*/
}
for(i=0,k=0;i<10;i++){ //将每次排序后的数组覆盖原数组
if(order[i]){ //如果当前位上有元素
//按照从低位到高位,从前到后依次覆盖原数组
for(j=0;j<order[i];j++,k++) a[k]=bucket[i][j];
}
}
}
}
int MaxDigit(ElementType a[],int length){
int i;
ElementType max=a[0];
for(i=0;i<length;i++) if(a[i]>max) max=a[i];
for(i=0;max>0;max/=10,i++);
return i;
}
3、基数排序的分析
3.1 时间复杂度分析
基数排序总共执行了最高位轮,假设最高位为d,每一个数位有k种可能的取值,元素总数为n。每一轮执行了n+k次,所以总的运行时间为O(d(n+k))。
3.2 运行实例分析
在这里我生成一个从1——N的随机数数组,N的选取为10000,1000000,1000000,10000000,太小了运行时间太短,注意要在main函数外面声明数组,main函数中声明的数组长度不能太大,因为main函数里面是局部变量放在堆栈段,局部变量太大会导致栈溢出。
生成随机数组的函数:
vvoid Randomize(ElementType a[],int length){
memset(a,0,sizeof(a));
srand((ElementType)time(NULL));
for(int i=0;i<length;i++) a[i]=i+1;
for(int i=length-1;i>0;i--) Swap(&a[i],&a[rand()%i]);
}
void Swap(ElementType* a,ElementType* b){
ElementType temp=*a;
*a=*b;
*b=temp;
}
不同数组长度下的10次运行平均时间:
10000 | 100000 | 1000000 | 10000000 |
---|---|---|---|
0.001 | 0.0087 | 0.0705 | 0.789 |
4、整体代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
typedef int ElementType;
const int N=1000000;
ElementType num[N];
ElementType bucket[10][N]; //临时数组
void Swap(ElementType*,ElementType*); //交换函数
void Randomize(ElementType [],int); //生成随机数数组
void RadixSort(ElementType [],int); //基数排序函数
int MaxDigit(ElementType [],int); //返回一个数组最大值位数
int main(void){
clock_t start=0,finish=0;
Randomize(num,N);
start=clock();
RadixSort(num,N);
finish=clock();
//for(int i=0;i<N;i++) printf("%d\n",num[i]);
printf("Time is:\n%lf",(double)(finish-start)/CLOCKS_PER_SEC);
return 0;
}
void RadixSort(ElementType a[],int length){
int n,times=MaxDigit(a,length),order[10]; /*times 表示最大值有几
位,总循环多少次,order数组用来记录每一行上的元素个数 */
//ElementType bucket[10][length]; //临时数组
memset(bucket,0,sizeof(bucket));
int i,j,k,digit=1,lsd; /*digit表示当前比较位,lsd表示当前元素
在比较位上的值,lsd表示从最低位开始比较*/
for(n=0;n<times;n++,digit*=10){
memset(order,0,sizeof(order)); //每次循环都要将order数组置0
for(i=0;i<length;i++){
lsd=(a[i]/digit)%10; //获取当前元素在当前比较位上的值
bucket[lsd][order[lsd]++]=a[i]; /*行是当前比较位上的值从0-9,
order[lsd]的值则是比较位为lsd的元素的个数*/
}
for(i=0,k=0;i<10;i++){ //将每次排序后的数组覆盖原数组
if(order[i]){ //如果当前位上有元素
//按照从低位到高位,从前到后依次覆盖原数组
for(j=0;j<order[i];j++,k++) a[k]=bucket[i][j];
}
}
}
}
int MaxDigit(ElementType a[],int length){
int i;
ElementType max=a[0];
for(i=0;i<length;i++) if(a[i]>max) max=a[i];
for(i=0;max>0;max/=10,i++);
return i;
}
void Randomize(ElementType a[],int length){
memset(a,0,sizeof(a));
srand((ElementType)time(NULL));
for(int i=0;i<length;i++) a[i]=i+1;
for(int i=length-1;i>0;i--) Swap(&a[i],&a[rand()%i]);
}
void Swap(ElementType* x,ElementType* y){
ElementType temp=*x;
*x=*y;
*y=temp;
}
总结
基数排序是基于桶排序的一种分配式排序算法,在一些情况下具有线性时间,但是其对空间的浪费比较大,此外利用计数排序作为中间稳定排序的基数排序不是原址排序,当主存的容量比较宝贵时就不太适合。