基数排序(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("\n重新排列:");
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("\n 排序后:");
61     for(i = 0; i <n; i++)
62         printf("%d ",data[i]);
63     printf("\n");
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 个指针域的空间。

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

  

  

 

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

posted on 2013-10-22 12:02  surgewong  阅读(3320)  评论(0编辑  收藏  举报