排序1+3:基数排序(RadixSort),希尔排序(ShellSort)和快速排序(QuickSort)

严重声明:此贴转自http://www.cnblogs.com/FlyingBread/archive/2007/01/31/635123.html ,转发请注明出处。

 

排序1+3:基数排序(RadixSort),希尔排序(ShellSort)和快速排序(QuickSort)

1 基数排序

基数排序对于整数特别有效。是一种稳定的算法(意思是相同的数字不会交换关系)。基数排序是根据数字的性质来逐步根据个位数,十位数,百位数分类求得排序结果的方法之一。它的想法如下:

(1)先将数字根据A[n]依个位数来分类,放入含有数字0,1,2,...,9的临时数组D[10][n]中,再按照数字大小顺序放回原数组。那么这时候数据已经按照个位数大小从小到大排序。

(2)同样将数字按照百位数,千位数,万位数排序,.....最后就可以得到排好序的数字。

假设有下面4个数字需要排序:123 42 765 64

第一次按照个位数的大小排序后:42 123 64 765

第二次按照十位数的大小排序后:123 42 64 765 

的三次按照百位数的大小排序后:42 64 123 765,其中42,64都小于100,因此其百位数可以看成0。

从上面的算法描述可以看出,我们首先需要知道一个数据系列中的最大数据。接下来,我们还要知道它有多少位,最后我们必须知道每一位到底是什么。比如例子中最大的数据是765,我们需要765是一个3位数,而且需要知道它的个位是5,十位是6,百位是7。

取得一个数据系列中最大值是很容易的。可以用下面的函数:

        ///   <summary>
         ///  /找到一个数据序列中最大的数
         ///   </summary>
         ///   <param name="myArray"> 输入的整数序列 </param>
         ///   <returns> 数据序列中最大的整数 </returns>
         private   static   int  FindMax( int  [] myArray)
        {
             int  max  =  myArray[ 0 ];

             for ( int  i = 1 ; i < myArray.Length; i ++ )
            {
                 if ( max  <  myArray[i] )
                    max  =  myArray[i];
            }

             return  max;
        }

我们要知道一个数据的位数也很容易。可以用下面的函数取得一个整数有多少位。

        ///   <summary>
         ///  取得一个整数的位数。比如123, 返回3。
         ///  利用整数除法的性质,不停地用10除以整数。
         ///  比如:123/10 = 12,12/10 = 1,1/10 = 0。
         ///  一共除了3次,那么就是一个3位数。
         ///   </summary>
         ///   <param name="number"> 输入的整数 </param>
         ///   <returns> 数字的位数,比如123 返回 3 </returns>
         private   static   int  DigitNumber( int  number)
        {
             int  digit  =   0 ;

             do  
            {
                number  /=   10 ;
                digit ++ ;
            }  while ( number  !=   0  );

             return  digit;
        }

如果我们要知道一个整数的每一位,也是很简单的,比如我们需要知道765的每一位。首先765%10=5,就是个位数,然后765/10=76,76%10=6,就是十位数,765/100=7,7%10=7,就是百位数,因此可以根据简单的公式得到每一位。m=n/(10^i),m%10就是第i位数字。算法如下:

        ///   <summary>
         ///  得到一个整数的某一位数,0表示个位数,1表示十位数
         ///  2表示百位数,等等。
         ///  利用的算法是求余。比如12/1 = 12,12%10 = 2,2就是个位数
         ///  12/10 = 1,1%10 = 1,1就是十位数,那么可以得到一个简单的公司
         ///  令n=n/(10^kth),那么n%10就是所要求的位数
         ///   </summary>
         ///   <param name="number"> 输入的整数 </param>
         ///   <param name="kth"> 某位数字,0表示个位,1十位,2百位,依此类推 </param>
         ///   <returns> 返回第k位数字 </returns>
         private   static   int  KthDigit( int  number,  int  Kth)
        {
                number  /=   ( int )Math.Pow( 10 , Kth);

                 return  number  %   10 ;
        }

从上面的算法思想可以得到算法的实现如下:

        ///   <summary>
         ///  基数排序的中心思想就是将数字按照个位,十位,百位.分别排好序
         ///  那么整个数组也就有序了。
         ///  比如我们先将数组安装个位数字进行排序,然后再将这个数组安装十位排序
         ///  依次下去,我们就可以将整个数组进行排序
         ///   </summary>
         ///   <param name="myArray"> 输入的未排序数列 </param>
         public   static   void  RadixSort( int  [] myArray)
        {
             int  [] count  =  { 0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 };
             int [,] temp  =   new   int [ 10 , myArray.Length];

             int  max  =  FindMax( myArray );                       // 取得序列中的最大整数
             int  maxDigit  =  DigitNumber( max );               // 得到最大整数的位数

             int  i, j, k;
             for (i = 0 ; i < maxDigit; i ++ )
            {
                 for (j = 0 ; j < 10 ; j ++ )
                    count[j]  =   0 ;

                 for (j = 0 ; j < myArray.Length; j ++ )
                {
                     int  xx  =  KthDigit( myArray[j], i );              // 将数据安装位数放入到暂存数组中
                    temp[xx,count[xx]]  =  myArray[j];
                    count[xx]  =  count[xx]  +   1 ;
                }

                 int  index  =   0 ;
                 for (j = 0 ; j < 10 ; j ++ )                                     // 将数据从暂存数组中取回,放入原始数组中
                {
                     for (k = 0 ; k < count[j]; k ++ )
                    {
                        myArray[index]  =  temp[j,k];
                        index ++ ;
                    }
                }
            }
        }

基数排序法的时间为O(n logRB)。其中B是数字个数(0-9),R是基数位数(最大值数据的位数),相同数据在基数排序法的过程中的位置不会变动,是一种稳定的排序的算法。但是如果数据量很大的话,这种算法也需要很多额外的存储空间。不过从总的看来,这种算法对于整数排序还是很好的。

2 Shell排序(希尔排序)

Shell排序属于交换方法的排序算法。从前面介绍的冒泡排序法,交换排序法,选择排序法,插入排序法4者可以发现,如果数据已经大致排好序的时候,其交换数据位置的动作将会减少。例如在插入排序法过程中,如果某一整数d[i]不是较小时,则其往前比较和交换的次数会更少。

如何用简单的方式让某些数据有一定的大小次序呢?Donald Shell(Shell排序的创始人)提出了先将数据按照固定的间隔分组,例如每隔4个分成一组,然后排序各分组的数据,形成以分组来看数据已经排序,从全部数据来看,较小值已经在前面,较大值已经在后面。将初步处理了的分组再用插入排序来排序,那么数据交换和移动的次数会减少。可以得到比插入排序法更高的效率。

假设有12个数据:71 101 81 111 51 11 91 21 121 61 31 43

分成4组

第一组 71 51 121

第二组 101 11 61

第三组 81 91 31

第四组 111 21 41

当我们把各个组的数据都排好序以后,再对总的数据进行排序,那么就可以看到数据的移动次数比起普通的插入排序会少了很多。

如何进行分组呢?按照Shell的分法,h(0)=[n/2],h(i+1)=h(i)/2。按照上面的思路,可以得到排序算法如下。

        ///   <summary>
         ///  Shell排序的中心思想是将数据进行分组,然后对每一组数据进行排序,
         ///  在每一组数据都有序之后,就可以对所有的分组利用插入排序进行最后一次排序
         ///  这样可以显著减少数据交换的次数,以达到加快排序速度的目的。
         ///   </summary>
         ///   <param name="myArray"> 输入的待排序序列 </param>
         public   static   void  ShellSort( int  [] myArray)
        {
             int  i, j, increment;
             int  temp;

             for ( increment  =  myArray.Length  /   2 ; increment  >   0 ; increment  /=   2  )
            {
                 for ( i  =  increment; i  <  myArray.Length; i ++  )
                {
                    temp  =  myArray[i];

                     for ( j  =  i; j  >=  increment; j  -=  increment )
                    {
                         if ( temp  <  myArray[j - increment] )
                            myArray[j]  =  myArray[j - increment];
                         else
                             break ;
                    }
                    myArray[j]  =  temp;
                }
            }
        }

在Shell之后,D.E.Knuth提出了一种新的分组方法:h(m+1)=3h(m)+1,h(1)=1,已经证明这种分组方法对数据量很大的时候很合适,而且可以避免一些原始分组方法的弊端,因此这种分组方法就成了Shell分组方法的标准分法。按照这种分组方法,可以对Shell排序改进如下:

        ///   <summary>
         ///  Shell排序的中心思想是将数据进行分组,然后对每一组数据进行排序,
         ///  在每一组数据都有序之后,就可以对所有的分组利用插入排序进行最后一次排序
         ///  这样可以显著减少数据交换的次数,以达到加快排序速度的目的。
         ///  新的算法将增量固定为3,也就是满足了D.E.Knuth的h(m+1)=3h(m)+1,h(1)=1
         ///   </summary>
         ///   <param name="myArray"> 输入的待排序序列 </param>
         public   static   void  ShellSort(  int  [] myArray )
        {
             int  i, j, increment, temp;

            increment  =   3 ;

             while ( increment  >   0  )
            {
                 for ( i = 0 ; i  <  myArray.Length; i ++  )
                {
                    j  =  i;
                    temp  =  myArray[i];

                     while ( (j  >=  increment)  &&  (myArray[j - increment]  >  temp) )
                    {
                        myArray[j]  =  myArray[j  -  increment];
                        j  =  j  -  increment;
                    }

                    myArray[j]  =  temp;
                }

                 if ( increment / 2   !=   0  )
                {
                    increment  =  increment / 2 ;
                }
                 else   if ( increment  ==   1  )
                {
                    increment  =   0 ;
                }
                 else
                {
                    increment  =   1 ;
                }
            }
        } 


3 快速排序

快速排序是最有名且最常用的排序算法之一,因为它的时间复杂度为O(nlgn),而且可以按照递归的思路来设计程序,它的想法如下:一般的排序方法(冒泡排序法,交换排序法,选择排序法,插入排序法)一次都只能减少一个数据量,而Shell排序法每次都按照分组来排序,相当于减少了较多的数据量,如果每次都大幅度地减少数据量,那么效率会更高。

如果可以在排完一个数据后,使其余数据分成两部分,一部分都比它大,另外一部分都比它小,再分别排序两组数据,那么效果会更好。快速排序就是利用了这个思路。

如果第一次先以第0个数据为比较值,用pos代表其下标,希望可以把pos放到合适的位置k,使右边的数据都比它小,左边的数据都比它大。

要达到这个目标,可以按照下面的办法来处理。

(1)一方面,我们从左边开始,向右去找一个比pos大的数据,设其位置为lower,然后从右边开始,向左去寻找一个比pos小的数据,设其位置为upper。然后我们交换lower和upper。接下来继续寻找,找到符合条件的就交换。直到upper小于lower为止,这时候说明右边的数据都小于pos,左边的数据都大于pos。

以pos的位置将数据分成两边,再按相同的办法来处理两边的数据直到所有的数据结束。

其算法如下:

    public   class  QuickSorter
    {
         private   static   int [] myArray;
         private   static   int  arraySize;

         public   static   void  Sort(  int [] a )
        {
            myArray  =  a;
            arraySize  =  myArray.Length;
            QuickSort();
        }

         ///   <summary>
         ///      快速排序可以用递归的办法来设计程序。其基本思想如下:
         ///      首先,找到一个基准的数据,是数据右边的数都比它小,左边都比它
         ///      大,然后用这个基准将所有数据分成两部分,再对每一部分使用同样
         ///      的算法。
         ///      如何找到基准数据呢?从第0个数据开始,从数据的右端向左寻找比它大的数,
         ///      同时从数据的左端向右寻找比它小的数,然后交换两个数,这样一直找下去
         ///      直到左端数据的索引大于右端的数据索引,然后将第0个数和左端的数据索引交换
         ///      这样就得到了数据右边的数都比它小,左边的都比它大。
         private   static   void  QuickSort()
        {
            QSort(  0 , arraySize  -   1 );
        }

         private   static   void  QSort( int  left,  int  right)
        {
             int  pivot, l_hold, r_hold;

            l_hold  =  left;
            r_hold  =  right;
            pivot  =  myArray[left];
             while  (left  <  right)
            {
                 // 从右向左找小于pivot的值,然后将它和左边的值交换,直到
                 // 相遇为止
                 while  ((myArray[right]  >=  pivot)  &&  (left  <  right)) 
                    right -- ;
                 if  (left  !=  right)
                {
                    myArray[left]  =  myArray[right];
                    left ++ ;
                }
                 // 从左向右找大于pivot的值,然后将它和右边的值交换,直到
                 // 相遇为止
                 while  ((myArray[left]  <=  pivot)  &&  (left  <  right))
                    left ++ ;
                 if  (left  !=  right)
                {
                    myArray[right]  =  myArray[left];
                    right -- ;
                }
            }
             // 将pivot放在中间,现在左边的值都小于pivot,右边的值都大于pivot
            myArray[left]  =  pivot;
            pivot  =  left;
            left  =  l_hold;
            right  =  r_hold;
             // 递归,直到最后一个元素
             if  (left  <  pivot)
                QSort(left, pivot - 1 );
             if  (right  >  pivot)
                QSort( pivot + 1 , right);
        }
    }

可以象下面来引用:

QuickSorter.Sort( myArray );

4 结果比较

通过实际的排序比较表明,在数据量达到50000的时候,QuickSort是最快的算法,RadixSort其次,ShellSort最慢。但是考虑到RadixSort只能排序整数,而且需要很多的暂存空间,ShellSort和QuickSort都是不错的算法。当数据量很大的时候,QuickSort是最好的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值