• 详谈基数排序


    目录

    0. 基数排序由来

    1. 基数排序图解

    2. 基数排序代码

    3. 基数排序测试

    4. 小结

    5. 参考资料

    0. 基数排序由来

    有一个学生记录数组,数组元素包含三个字段:系别,班号,班内编号。现在要对这个数组排序,使得系别小的在前,系别相同的班号小的在前,班号相同则班内编号较小的在前。这个排序与一般的排序要求(只对某个关键字段排序)不同的是:对多个关键字段排序。有2种方法可解决:一种是扫描一遍记录,将之按系别分组,再对组内记录按班号分组,然后对组内记录进行单关键字排序;另一种方法是,将这个数组按班内编号排序(班内编号在确定的范围内),然后再对整个数组按班号排序(班号在确定的范围内),最后对整个数组按系别排序(系别在确定的范围内)。第一种方法采取的策略是最高位优先排序。第二种方法采取分步骤按关键字类别排序的策略通常称为基数排序,可以看出基数排序的特点是各关键字段的分布是在确定范围(一般较小)内的。

    对单关键字排序也可采用多关键字排序的思想:将单关键字分解成多个关键字段,在可控范围内,不用比较,直接排序。

    计算机实现基数排序方式:分配和收集。按关键字段将记录重新分成若各组,再将各个组有序组合成新记录序列。

    1. 基数排序图解

    1.1 基数排序图示

    1) 辅助队列实现基数排序

    不足:需要额外利用队列来辅助排序,存在空间浪费。

    2)链队列实现基数排序

     

    3)基于桶有序的基数排序(也称计数排序,统计子关键字的出现频率)

    考虑一种简单情况,如果你给一些unsigned char 排序,除了教科书上的很多方法外,还有一种简单的。可以这样考虑,试想排一系列的unsigned char, 值从0~ 255 , 放在 u_char data_array[ARRAY_SIZE] 中,分配一个数组 int membuffer[256]; 用于统计每个元素出现的次数,也就成为了数组的排序结果。

    1 for(int i=0;i<256;i++) 
    2   membuffer[i]=0; 
    3 for(int i=0;i<ARRAY_SIZE;i++) 
    4   membuffer[data_array[i]]++; 

    1.2 基数排序分析

    基数排序的优点就是:不用比较,每个关键字的范围确定且一般较小,排序时间复杂度低。但为什么基数排序的应用范围没有快速排序那样广呢?原因有以下几点:

    1)基数排序一般需要额外的存储空间:顺序队列实现需要O(n)的元素空间,链队列实现需要O(n+2d)个指针空间,计数实现也需要O(n)的元素空间

    2)基数排序的时间复杂度看似O(n),但它每次排序都是对整个数组的处理,相对快排来讲,cache局部性不好。

    在我的印象中,基数排序一般出现在面试中,在实际生产环境中还没有发现采用基数排序。

    2. 基数排序代码

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<string.h>
     4 
     5 // 基于无符号整数的基数排序
     6 void radix(int byte, unsigned int N, const unsigned int *source, unsigned int *dest) {
     7   unsigned int count[256];
     8   unsigned int index[256];
     9   int i = 0;
    10 
    11   memset(count, 0, sizeof (count));
    12 
    13   // 统计出现频率
    14   for (i=0; i < N; ++i) {
    15     ++count[((source[i])>>(byte*8))&0xff]; // 以一个字节为单位
    16   }
    17 
    18   // 为dest下标初始化做准备
    19   index[0] = 0;
    20   for (i = 1; i < 256; ++i) {
    21     index[i] = index[i-1] + count[i-1];
    22   }
    23 
    24   // 数组重排列
    25   for (i = 0; i < N; ++i) {
    26     dest[index[((source[i])>>(byte*8))&0xff]++] = source[i];
    27   }
    28 }
    29 
    30 void radixsort (unsigned int *source, unsigned int *temp, unsigned int N) {
    31   radix(0, N, source, temp);
    32   radix(1, N, temp, source);
    33   radix(2, N, source, temp);
    34   radix(3, N, temp, source);
    35 }
    36 
    37 
    38 int main(int argc, char** argv) {
    39   unsigned int a[5] = {300, 200, 2,20, 5};
    40   unsigned int len = 5;
    41   unsigned int b[5] = {0};
    42   unsigned int i = 0;
    43 
    44   radixsort(a, b, len);
    45   for (; i < 5; ++i) {
    46     printf("%ld ", a[i]);
    47   }
    48 
    49   return 0;
    50 }

    3. 基数排序测试

    1)上节代码的运行结果如下:

    2)网友的性能对比测试:

    引用网络上的测试数据:

    今实测性能在n =1024*1000的时候,VC 6 上 release 模式,排序时间:(tick)

    n                           Radix Sort                  STL Sort
    100*1024              20                              20
    1000*1024            250                            320
    4000*1024            991                            1513
    10000*1024          2093                           3606

    从以上数据可见Radix Sort 的确可能比STL Sort 快,而且,因为Radix Sort时间复杂度小,在更长的时间内表现更为充分。n 从100* 1024增长到10000*1024, 排序时间也从20 增长到2093, 可以看出的确是O(n)的。

    4. 小结

    基数排序采用多关键字的排序思想,由最低位到最高位逐步处理各关键字断序列,达到了O(n)的时间复杂度,但增加了空间复杂度。

    基数排序一般适用于已知关键字的分布情况,且分布比较集中。计算机实现一般采用分配-收集策略或计数方式(统计各关键字的出现频率)。

    5. 参考资料

    Radix Sort 的介绍

  • 相关阅读:
    linux报错-bash: ./xx.sh: Permission denied
    shell脚本报错:-bash: xxx: /bin/bash^M: bad interpreter: No such file or directory
    点灯
    笑话
    bzoj 4898: [Apio2017]商旅
    bzoj 4446: [Scoi2015]小凸玩密室
    bzoj 4237: 稻草人
    idea
    springmvc集成swagger实现接口文档自动化生成
    基本的内存泄漏的解释
  • 原文地址:https://www.cnblogs.com/didiaoxiong/p/3259987.html
Copyright © 2020-2023  润新知