• 基数排序简述


      在学习了计数排序后,可以发现一个很严重的问题,如果数据很大呢,不如说每个元素小于2^64 - 1,不仅超时,而且正常数据下内存直接炸了。

      (虽然可以直接用快排,但是为了讲解基数排序还是讲一下基数排序)

      基数排序可以说成是改良版的桶排(有点类似,基数排序还是属于一种"分配式"排序),还是将一些数放入指定的桶中

    比如一串数  12  33  43  54  65  39  45  72  89

    首先按照个位分配到 0 - 10 这11个“桶”中

     0
     1 
     2 12  72
     3 33  43
     4 54
     5 65  45
     6
     7 
     8 
     9 39  89

    接着再串起来,得到了下面这样的数列

    12    72    33    43    54    65    45    39    89

    再按照十位进行分配桶

     0
     1 12
     2 
     3 33  39
     4 43  45
     5 54
     6 65
     7 72
     8 89
     9 

    同上,再把这些桶串起来

    12    33    39    43    45    54    65    72    89   

    这时候的数列已经是有序的了,不需要下一轮的分配了

    这样不如说成从低位到高位,按位排序

    可能可以想到,有没有可能出现从小到大排序出现这样的情况

      46  42

    这样是不会出现的,稍微想一下就可以明白

      先按个位排序,46第一次排序完了一定是在42的后面

      再按十位排序,在4这个桶内46一定是42后面,因为是按顺序放在后面

    上面这种方法又叫做LSD(最低位优先)法,先给出LSD的实现(中间用到了

    计数排序,和上面有一点不同,但是仍然不难理解)

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 int* counter;
     6 int* buf;
     7 int maxbit(int* array, int len){
     8     int result = 1;
     9     int p = 1;
    10     for(int i = 0;i < len;i++){
    11         while(((p << 3) + (p << 1)) <= array[i]){
    12             p = (p << 3) + (p << 1);        //p *= 10; 
    13             result++;
    14         }
    15     }
    16     return result;
    17 }
    18 void radixSort(int* &array, int len){
    19     buf = new int[(const int)(len + 1)];
    20     counter = new int[11];
    21     int limit = maxbit(array, len);
    22     int p = 1;
    23     for(int i = 0;i < limit;i++){
    24         memset(counter, 0, sizeof(int) * 11);
    25         for(int j = 0;j < len;j++)
    26             counter[(array[j] / p) % 10]++;
    27         for(int j = 1;j < 11;j++)
    28             counter[j] += counter[j - 1];
    29         for(int j = len - 1;j >= 0;j--)
    30             buf[--counter[(array[j] / p) % 10]] = array[j];
    31         swap(array, buf);
    32         p = (p << 3) + (p << 1);
    33     }
    34     delete[] buf;
    35     delete[] counter;
    36 }
    37 int n;
    38 int *a;
    39 int main(){
    40     cin>>n;
    41     a = new int[(const int)n];
    42     for(int i = 0;i < n;i++)
    43         cin>>a[i];
    44     radixSort(a, n);
    45     for(int i = 0;i < n;i++)
    46         cout<<a[i]<<" ";
    47     return 0;
    48 } 
    LSD

      如果从高位开始按位排序又叫做MSD(最高位优先)法

    如果您按照上面那种方法去做,很快就可以发现在对于这样一组数据是不行的

      32  13  12

      MSD的做法除了顺序外还有不同之处,如果一个桶中的元素不止一个,就对这个桶中

    的元素进行下一位的排序

      例如上面这组数据,首先第一轮分配

    0
    1 13  12
    2 
    3 32

    1这个桶内的元素不止一个,递归调用进行第二轮分配,分配个位

    0 
    1 
    2 12
    3 13

    发现所有的桶中的元素都没有超过1个,返回(另外还要拷贝并覆盖原来那

      如果像LSD那样用计数排序(省内存),貌似不太好处理,不过请仔细观察counter数组

    在排序的过程中,counter对应位上值会减去一,排序完后这一位相当于前一位原来的值

      如果counter[i] != counter[i + 1]说明i这个桶中有东西,计算有多少个东西就直接

    counter[i + 1] - counter[i]就能算出来了,起始位置就是array[counter[i]],这样就解决

    另外还要注意,如果是最低位就不用再递归调用了(如果忘记这一点可能不会超时,应该很快就

    会整数被0除)

      附上MSD的代码(也没改多少)

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 int* buf;
     6 int p;
     7 int maxbit(int* array, int len){
     8     int result = 1;
     9     p = 1;
    10     for(int i = 0;i < len;i++){
    11         while(((p << 3) + (p << 1)) <= array[i]){
    12             p = (p << 3) + (p << 1);        //p *= 10; 
    13             result++;
    14         }
    15     }
    16     return result;
    17 }
    18 /**
    19  * 基数排序MSD法 
    20  * @param array 数组的起始位置
    21  * @param len 长度
    22  * @param status 辅助作用 
    23  */ 
    24 void radixSort(int* array, int len, int status){
    25     buf = new int[(const int)(len + 1)];
    26     int* counter = new int[11];            //一定要定成局部变量 
    27     memset(counter, 0, sizeof(int) * 11);
    28     for(int j = 0;j < len;j++)
    29         counter[(array[j] / status) % 10]++;
    30     for(int j = 1;j < 10;j++)
    31         counter[j] += counter[j - 1];
    32     for(int j = len - 1;j >= 0;j--)
    33         buf[--counter[(array[j] / status) % 10]] = array[j];
    34     for(int i = 0;i < len;i++)
    35         array[i] = buf[i];
    36     delete[] buf;
    37     for(int i = 0;i < 9;i++){                /* 这里开始 */ 
    38         int l = counter[i];
    39         int r = counter[i + 1] - 1;
    40         if(l < r && status > 1){
    41             radixSort(array + counter[i], r - l + 1, status / 10);
    42         }
    43     }                                        /* 这里结束 */ 
    44     delete[] counter;
    45 }
    46 int n;
    47 int *a;
    48 int main(){
    49     cin>>n;
    50     a = new int[(const int)n];
    51     for(int i = 0;i < n;i++)
    52         cin>>a[i];
    53     maxbit(a, n);
    54     radixSort(a, n, p);
    55     for(int i = 0;i < n;i++)
    56         cout<<a[i]<<" ";
    57     return 0;
    58 } 
    MSD

    [后话]

      看完之后,仔细思考,应该可以发现一个很严重的问题——不能给负数排序

    那怎么办呢?

    那我就说说的几种方法(当然差值不能太大)

      1)全部加上最小的负数的相反数,输出时候减去这么多

      2)将非负整数用一个数组存储,将负数用另外一个数组存储,负数并且全部取反(是数学上的-)

       先将前者排序,再将后者用同一个函数排序,输出的时候先输出后者,从高下标往低下标输出,

       并把负号添上,再输出前者,从低下标到高下标


      基数排序给整数排序的时候并不是用得特别多,更多的时候是用于比较复杂度不是O(1)(多关键字)的时候排序,比如说年月日等等,

    后缀数组的构造(当然要用倍增)用基数排序也会比用快排快很多

  • 相关阅读:
    MySQL启动和关闭命令总结
    MySQL数据库5.6版本首次安装Root密码问题
    tomcat 9性能调优注意事项
    扫除减脂之路上的几个小障碍
    MySQL常见面试题
    关于邮箱发送邮件二之附件及图片
    关于邮箱发送邮件
    关于算法
    python中常见的数据类型
    C++实现复数类的输入输出流以及+-*/的重载
  • 原文地址:https://www.cnblogs.com/yyf0309/p/5750609.html
Copyright © 2020-2023  润新知