无聊写排序之 ----基数排序(RadixSort)

来源:互联网 发布:linux 查看网络日志 编辑:程序博客网 时间:2024/06/05 19:52

基数排序(RadixSort)

     基数排序也是一种不需要元素间的比较进行排序的排序算法,而是通过对数据的分配和合并来实现的排序,也叫做桶子法排序,通过对元素按照键值进行分配到指定标准的桶中,在进行合并收集从而达到排序的结果,是一种稳定排序,基数排序的时间复杂度是O(k·n)其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n))k的大小取决于数字位的选择(比如比特位数),和待排序数据所属数据类型的全集的大小;k决定了进行多少轮处理,而n是每轮处理的操作数目。

以排序n个不同整数来举例,假定这些整数以B为底,这样每位数都有B个不同的数字,k = logB(N)N是待排序数据类型全集的势。虽然有B个不同的数字,需要B个不同的桶,但在每一轮处理中,判断每个待排序数据项只需要一次计算确定对应数位的值,因此在每一轮处理的时候都需要平均n次操作来把整数放到合适的桶中去,所以就有:

·        k约等于logB(N)

所以,基数排序的平均时间T就是:

T= logB(Nn

其中前一项是一个与输入数据无关的常数,当然该项不一定小于logn

如果考虑和比较排序进行对照,基数排序的形式复杂度虽然不一定更小,但由于不进行比较,因此其基本操作的代价较小,而且在适当选择的B之下,k一般不大于logn,所以基数排序一般要快过基于比较的排序,比如快速排序。

基数排序的原理:基数排序将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,针对每一位上的一个数字依次进行一次排序。排序完成后该位基本上处于有序状态,这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

举个栗子:73  22  93  43  55  14  28  65  39  81

1>     先根据个位数的数值,依次将它们分配至编号0到9的桶子中:

                                                  

      2> 接下来将所有桶中元素按照桶号由小到大依次重新收集串起来,看到数据如下还是乱序:

81  22  73  93  43  14  55  65  28  39

      3> 接着根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:


                                                   

4> 接下来将所有桶中元素按照桶号由小到大依次重新收集串起来,看到数据已经排序完成, 如下所示:

14  22  28  39  43  55  65  73  81  93

        可以看到,将所有数字位上的数据进行排序之后,我们最终将得到有序的数据序列。同理如果排序的数据序列有多位数,则重复进行以上的动作直至最高位数为止就可以得到有序的数据。

   一般基数排序有两种相反的方式进行,分别是LSD和MSD:

   最低位优先(Least Significant Digit first)法,简称LSD法。先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

   最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

   LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个桶子中建立子桶,将每个桶子中的数值按照下一数位的值分配到子桶中。在进行完最低位数的分配后再合并回单一的数组中

 

LSD实现

1>   LSD先依据元素最低位kd作为键值对所有元素进行一趟排序,排序过程使用计数排序

2>   紧接着再依据次低位关键码kd-1对上一趟排序的结果再排序,

3>   重复上述步骤,直到依据关键码K1最后一趟排序完成,就可以得到一个排好序的序列。

示例代码如下:

int getdigit(int x,int d)  {   int a[] = {1, 1, 10, 100};   //假设现在限制在三位数 return (x/a[d]) % 10;  }/** LSD 方法进行基数排序* 输入:待排序数组, 数组长度n,数组元素最大位数l* 输出:无*/void lsd_radix_sort(int* arr, int n, int l){const int radix = 10;int count[radix];int *bucket = (int *)malloc(sizeof(int)*n);for (int b = 1; b <= l; b++){// 遍历各个数字位 循环里面就是个计数排序 // 根据各个位的数字进行排序for (int i = 0; i < radix; i++)count[i] = 0;for (int i = 0; i < n; i++)count[getdigit(arr[i], b)]++;for (int i = 1; i < radix; i++)count[i] += count[i-1];for (int j = n; j > 0; j--){int index = j-1;int key = getdigit(arr[index], b);bucket[count[key]-1] = arr[index];count[key]--;}// 将排序结果归并回原数组,准备下一次排序for (int i = 0 ; i < n; i++)arr[i] = bucket[i];}free(bucket);}

MSD实现

1>   MSD首先根据元素的最高位k1作为键值对所有元素进行一次排序,当然也是基于计数排序。

l 第一次排序完之后各个桶的位置已经固定了,很明显就可以看到元素大的最高位肯定大,那势必就会在靠后的桶子中。

根据上一条的结果可以说明,桶里面的是乱序,桶和桶之间已经是有序的,剩下只需要一次将各个桶内的元素进行有序就可以使得最终的结果有序。

2>   分别对于每个分组桶中的元素,根据次高位k2作为键值进行排序。对桶内的元素分成不同的子桶。对于子桶的元素均遵守k1,k2相同。

3>   根据上述原则进行进一步重复排序直到kd作为关键字,进行排序完,所有的元素按照桶序号串起来便是一个有序的序列。

4>   这个过程往往是一个递归的操作。


示例代码如下:

/** MSD 方法进行基数排序* 输入:待排序数组, 待排序数组首索引s和尾索引e,数组元素最大位数l* 输出:无*/void msd_radix_sort(int* arr, int s, int e, int l){const int radix = 10;int count[radix];int *bucket = (int *)malloc(sizeof(int)*(e-s+1));// 每个递归都是做一次基数排序对于指定的区间的数组for (int i = 0; i < radix; i++)count[i] = 0;for (int i = s; i <= e; i++)// 统计每个桶的个数count[getdigit(arr[i], l)]++;for (int i = 1; i < radix; i++) // 桶的左右边界索引count[i] += count[i-1];for (int j = e; j >= s; j--){int index = j;int key = getdigit(arr[index], l);bucket[count[key]-1] = arr[index];count[key]--;// 扩展到左边界,起始时是在有边界处[l,r);}for (int i = s ; i <= e; i++)arr[i] = bucket[i];free(bucket);// 针对桶里面的元素进行子桶划分进而进行排序for (int i = 0; i < radix; i++){int st = s + count[i];// 注意思考这里如何划分左右边界int et = s + count[i+1]-1;if (st < et && l > 1)// 对于没有元素的空桶进行忽略{msd_radix_sort(arr, st, et, l-1);}}}

    很明显基数排序也是一个稳定的非比较性排序,每一次数位排序都采用计数排序来加快速度,基数排序在大多数可用的应用场景下都可以获得很高的性能,尤其是应用在后缀数组的实现。


0 0
原创粉丝点击