zoukankan      html  css  js  c++  java
  • 基数排序

      基数排序(Radix Sorting),又称 桶排序(bucket sorting)是之前的各类排序方法完全不同的一中排序方法,在之前的排序方法中,主要是通过元素之间的比较和移动两种操作来实现排序的。基数排序不需要进行元素之间的比较,而是根据关键字的每个位上的有效数字的值,借助“分配”和 “收集”两种操作来进行排序的一中内部排序方法。

      在具体介绍基数排序算法前,首先先介绍两个两个关键词:单关键字多关键字。序列中任一记录的关键字均有 d 个分量 ki ki1  …… kid-1 构成,若 d 个分量中每个分量都是一个独立的关键字,则文件是多关键字的(如扑克牌有两个关键字:点数和花色、汉子的笔画和拼音);否则是文件时单关键字的(如数值和字符串)。在多关键字的排序中,每个关键字都能决定记录的大小,如扑克牌的花色黑桃比花色方片大;在花色相同的情况下,在比较点数的大小,如红桃 9 比红桃 8 大。 ki(0<= j < d) 是关键字中的其中一位(如字符串,十进制整数等)。多关键字中的每个关键字的取值范围一般不同,如扑克牌的花色取值只有 4 种,而点数则有 13 种。对于单关键字序排列可以利用多关键字排序的方法,只是单关键字的每位一般取值范围相同。

      在介绍一下基数的概念。设单关键字的每个分量的取值范围均为 C0 <= kj<= Crd-1 (0 <= j <=d),则每个记录中分量的可能取值的个数 rd 称为基数。基数的选择和关键字的分解因关键字的类型而异:

      (1)若关键字是十进制整数,则按个、十等位进行分解,基数 rd = 10, C0 = 0,C9 = 9, d 为最长整数的位数;

      (2)若关键字是小写的因为字符串,则 rd = 26,C0 = 'a' ,C25 = 'z', d为字符串的最大长度。

      (3)在扑克牌花色和点数的排序中,花色的基数 rd1 = 4 ,而点数的 rd2 = 13, d 同时使用扑克牌的副数。

      

     

      基数排序时一种借助于过关键字排序的思想,将单关键字按基数分成“多关键字”进行排序的方法。比如字符串 "abcd"  "acsc"  "dwsc"  "rews" 就可以把每个字符看成一个关键字,另外还有整数 425 、321、235、432也可以将每个位上的数字作为一个关键字。

      一般情况下,假定有一包含 n 个对象的序列 { V0 , V1 , …… ,Vn-1  } ,且每个对象 Vi   中包含 d 个关键字 ( ki1 ,ki2 , …… ,kid  ), 如果对于序列中任意两个对象 Vi  和 Vj    (0 <= i < j <= n - 1 ) 都满足: ( ki1 ,ki2 , …… ,kid  ) < ( kj1 ,kj2 , …… ,kjd  ) ,则称序列对关键字 ( k1 ,k2 , …… ,kd  )  有序。其中,k1  称为最高位关键字,k2  称为次高位关键字, kd  称为 最低位关键字。

      基数排序方法有两种:最高位优先法(MSD:Most Significant Digit First)最低位优先法(LSD:Least Significant Digit First)。

      最高位优先法,简称MSD法:即先按 k1 排序分组,同一组中记录的关键字 k1 相等,在对各组按  k2 分成子组,之后对其他的关键字继续这样的排序分组,直到按最次位关键字  k 对各子组排序后。再将各组连接起来,得到一个有序序列。

      最低位优先法,简称LSD法: 即先从 kd 开始排序,在对  kd-1 进行排序,一次重复,直到对 k1 排序后便得到一个有序序列。  

      现在已下面的序列为例简述一下 MSD 方法和 LSD方法: ead, bed, dad, add, bee, abc, dbe, dae, cda, eba, ccd    共 n = 11 个字符串

        上面的每个字符串每个字符串包含 3 个字符,因此 d = 3, 这些字符的取值 为 { a, b, c, d, e}  共 5 种取值, rd = 5

        【注:这是针对这个例子而言 rd = 5,字符串的 rd 一般为 26 】

        MSD 方法的排序过程如下:   

          第一个字母排序: 将第一个字母相同的元素放在同一个队列,我们就可以得到:

              第一个字母    元素

                a      add  abc

                b      bed  bee

                c      cda  ccd

                d      dad  dbe  dae

                e      ead  eda

          第二个字母排序:将上述队列中元素,第二个字母相同的元素放在同一个队列中,我们可以得到

              第一个字母   第二个字母    元素

                a        b      abc

                a        d      add

                b        e      bed  bee

                c        c      ccd

                c        d      cda

                d        a      dad  dae

                d        b      dbe

                e        a      ead

                e        b      eba

          第三个字母排序:将上述队里中的元素,第三个字母相同的元素放在同一个队列中,我们可以得到

              第一个字母   第二个字母    第三个字母   元素

                a        b        c     abc

                a        d        d     add

                b        e        d     bed

                b        e        e     bee

                c        c        d     ccd

                c        d        a     cda

                d        a        d     dad

                d        a        e     dae

                d        b        e     dbe

                e        a        d     ead

                e        b        a     eba

          由于每个栏中只有一个元素,故从上到下连接起来就得到有序队列: abc, add, bed, bee, ccd, cda, dad, dae, dbe, ead, eba

        使用LSD排序过程如下:

        首先根据第三个字母排序,字母相同的放在一起,得到序列: cda, eba, abc, ead, bed, dad, add, ccd, bee, dbe, dae

        然后对得到的序列根据第二个字母排序得到:ead, dad, dae, eba, abc, dbe, ccd, cda, add, bed, bee

        最后得到的序列根据第一个字母排列得到:  abc, add, bed, bee, ccd, cda, dad, dae, dbe, ead, eba

       我们可以看出使用LSD排序方法排序结果和 MSD排序方法是一样的,只是排序过程中元素交换次序有些区别。MSD 是对在上一次分配好的队列(或初始序列)中对元素进行分配排序,每一次分配的元素越来越少,总数不变,但是分配次数增加;LSD是对上一次分配得到的序列,收集后再进行分配排序,每一次分配元素和次数均相同。 

      上面讲得是关于基数排序的基本思路和方法,下面我们以另外一个例子讲一下基数排序的实现过程。

       我们以数值为例,先假定每个数值 只有两位,因此数值包括 d = 2 个分量, 每个数值的取值范围是 0 ~ 9 ,共 rd = 10 种,如数值:

          73, 22, 93, 43, 55, 14, 28, 65, 39,81

      首先根据个位数的数值,对每个数值查询最末位的值,将它们分配到编号0 ~ 9 的队列中

      

        个位数     数值

         0      

         1      81 

         2      22      

         3      43 93 73

         4      14

         5      65 55

         6      

         7

         8      28

         9      39

       将上面的队里的数值重新串起来,如果是链式队列的话,这个过程将会非常简单,只要把指针连接起来就好了。我们得到新的序列:

        81, 22, 43, 93, 73, 14, 65, 55, 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

      这个时候整个序列已经排序完毕,如果排序的对象有三位数以上,则继续进行以上的动作直至最高位为止。

      LSD的基数排序使用与位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方式恰好 与LSD相反,由最高位为基底进行分配其他的演算方式都相同。

      对于记录的长度不同的序列,通过在记录前面加上相应数据类型最低位来进行处理,如数值类列前加0,字符串类型前加空字符。

              

      参考代码:(这是以LSD方式实现的)

     1 #include <stdio.h>
     2 
     3 #define MAX_NUM 80
     4 
     5 int main(int argc, char* argv[])
     6 {
     7     int data[MAX_NUM];   // 存储数据 
     8     int temp[10][MAX_NUM];  // 队列表 
     9     int order[10]={    0};   // 用于记录队列的信息 
    10     
    11     int n;    // 待排序个数 
    12     int i,j,k;  // 用于排序 
    13     int d;  // 用于表示待排序输的位数 
    14     int lsd;  // 用于记录某位的数值 
    15     
    16     k = 0;
    17     d = 1;
    18     
    19     printf("输入需要排序的个数(<=80),待排序数(<10000): ");
    20     scanf("%d",&n);
    21 
    22     if( n > MAX_NUM ) n = MAX_NUM;
    23     
    24     for(i = 0; i < n;i++)
    25     {
    26         scanf("%d",&data[i]);
    27         data[i] %= 10000;
    28     }
    29         
    30     
    31     while(d <= 10000)
    32     {
    33         for(i = 0; i < n; i++)
    34         {
    35             lsd = (data[i]/d)%10;
    36             temp[lsd][order[lsd]] = data[i];
    37             order[lsd]++;
    38         }
    39         
    40         printf("
    重新排列:");
    41         
    42         for(i = 0; i < 10; i++ )
    43         {
    44             if(order[i]!=0)
    45             {
    46                 for(j = 0; j < order[i]; j++ )
    47                 {
    48                     data[k] = temp[i][j];
    49                     printf("%d ",data[k]);
    50                     k++;
    51                 }
    52             }
    53             order[i] = 0;
    54         }
    55         
    56         d *= 10;
    57         k = 0;
    58     }
    59     
    60     printf("
     排序后:");
    61     for(i = 0; i <n; i++)
    62         printf("%d ",data[i]);
    63     printf("
    ");
    64     
    65 
    66 
    67     return 0;
    68 }
    View Code

      

      代码运行结果以下面的数值列为例:  125 11 22 34 15 44 76 66 100 8 14 20 2 5 1  共 15 个元素

      运行截图:

      

      基数排序算法的效率和稳定性

      对于 n 个记录,执行一次分配和收集的时间为O(n+r)。如果关键字有 d 位,如果关键字有 d 位,则要执行 d 遍。 所以总的运算时间为 O(d(n+r))。可见不同的基数 r 所用时间是不同的。当 r 或 d 较小时,这种算法较为节省时间。上面讨论的是顺序表的基数排序,这个算法同样也是用与链式的记录排序,只是要求额外附加一下队列的头、尾指针。所以附加的存储量为 2r 个存储单元。待排序的记录以链表形式存储的,相对于顺序表,只是额外增加了 n 个指针域的空间。

      基数排序在分配过程中,对于相同关键字的记录而言保持原有顺序进行分配,故基数排序时稳定的排序方法。

      

      

      注:主要参考彭军、向毅主编的 《数据结构与算法》

  • 相关阅读:
    网络七层
    微信小程序开发工具 常用快捷键
    BZOJ 1026 windy数 (数位DP)
    BZOJ 1026 windy数 (数位DP)
    CodeForces 55D Beautiful numbers (SPOJ JZPEXT 数位DP)
    CodeForces 55D Beautiful numbers (SPOJ JZPEXT 数位DP)
    HDU 3709 Balanced Number (数位DP)
    HDU 3709 Balanced Number (数位DP)
    UVA 11361 Investigating Div-Sum Property (数位DP)
    UVA 11361 Investigating Div-Sum Property (数位DP)
  • 原文地址:https://www.cnblogs.com/surgewong/p/3381646.html
Copyright © 2011-2022 走看看