基数排序

简介

基数排序和桶排序有些像,都不需要比较数据大小,而其他排序算法要。

基数排序分为以下两类

一.最高位优先(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    
        }
    }
}

 

posted @ 2017-09-20 21:27  Bennettz  阅读(537)  评论(0编辑  收藏  举报