• 基数排序


    简介

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

    基数排序分为以下两类

    一.最高位优先(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    
            }
        }
    }
  • 相关阅读:
    单反相机的传奇—佳能单反50年辉煌之路(连载十五)
    单反相机的传奇—佳能单反50年辉煌之路(连载十二)
    单反相机的传奇—佳能单反50年辉煌之路(连载十四)
    单反相机的传奇—佳能单反50年辉煌之路(连载十六)
    GUID的使用
    C#中的活动目录开发
    C# 窗体桌面定位问题
    C#TCPClient应用一个简单的消息发送和接收
    SQL表间连接
    放弃VMware改投VirtualBox的五个理由
  • 原文地址:https://www.cnblogs.com/bennettz/p/7565205.html
Copyright © 2020-2023  润新知