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 个指针域的空间。

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

      

      

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

  • 相关阅读:
    JavaScript设计模式-21.命令模式
    JavaScript设计模式-20.责任链模式
    JavaScript设计模式-18.享元模式
    JavaScript设计模式-19.代理模式
    JavaScript设计模式-17.装饰者模式(下)
    JavaScript设计模式-16.装饰者模式(上)
    面向对象之集合ArrayList
    面向对象之继承
    字符串的添加与切割~~~
    面向对象中构造函数的小练习
  • 原文地址:https://www.cnblogs.com/surgewong/p/3381646.html
Copyright © 2011-2022 走看看