排序1+4:归并排序(MergeSort)和堆排序(HeapSort)

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

 

 

1 归并排序(MergeSort)

归并排序最差运行时间是O(nlogn),它是利用递归设计程序的典型例子。

归并排序的最基础的操作就是合并两个已经排好序的序列。

假设我们有一个没有排好序的序列,那么首先我们使用分割的办法将这个序列分割成一个一个已经排好序的子序列。然后再利用归并的方法将一个个的子序列合并成排序好的序列。分割和归并的过程可以看下面的图例。



从上图可以看出,我们首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。

如何把两个已经排序好的子序列归并成一个排好序的序列呢?可以参看下面的方法。

假设我们有两个已经排序好的子序列。
序列A:1 23 34 65
序列B:2 13 14 87
那么可以按照下面的步骤将它们归并到一个序列中。

(1)首先设定一个新的数列C[8]。
(2)A[0]和B[0]比较,A[0] = 1,B[0] = 2,A[0] < B[0],那么C[0] = 1
(3)A[1]和B[0]比较,A[1] = 23,B[0] = 2,A[1] > B[0],那么C[1] = 2
(4)A[1]和B[1]比较,A[1] = 23,B[1] = 13,A[1] > B[1],那么C[2] = 13
(5)A[1]和B[2]比较,A[1] = 23,B[2] = 14,A[1] > B[2],那么C[3] = 14
(6)A[1]和B[3]比较,A[1] = 23,B[3] = 87,A[1] < B[3],那么C[4] = 23
(7)A[2]和B[3]比较,A[2] = 34,B[3] = 87,A[2] < B[3],那么C[5] = 34
(8)A[3]和B[3]比较,A[3] = 65,B[3] = 87,A[3] < B[3],那么C[6] = 65
(9)最后将B[3]复制到C中,那么C[7] = 87。归并完成。

如果我们清楚了上面的分割和归并过程,那么我们就可以用递归的方法得到归并算法的实现。

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

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

         ///   <summary>
         ///  利用归并的方法排序数组,首先将序列分割
         ///  然后将数列归并,这个算法需要双倍的存储空间
         ///  时间是O(nlgn)
         ///   </summary>
         private   static   void  MergeSort()
        {
             int [] temp  =   new   int [arraySize];
            MSort( temp,  0 , arraySize  -   1 );
        }

         private   static   void  MSort( int [] temp,  int  left,  int  right)
        {
             int  mid;

             if  (right  >  left)
            {
                mid  =  (right  +  left)  /   2 ;
                MSort( temp, left, mid);  // 分割左边的序列
                MSort(temp, mid + 1 , right); // 分割右边的序列
                Merge(temp, left, mid + 1 , right); // 归并序列
            }
        }

         private   static   void  Merge(  int [] temp,  int  left,  int  mid,  int  right)
        {
             int  i, left_end, num_elements, tmp_pos;

            left_end  =  mid  -   1 ;
            tmp_pos  =  left;
            num_elements  =  right  -  left  +   1 ;

             while  ((left  <=  left_end)  &&  (mid  <=  right)) 
            {
                 if  (myArray[left]  <=  myArray[mid])  // 将左端序列归并到temp数组中
                {
                    temp[tmp_pos]  =  myArray[left];
                    tmp_pos  =  tmp_pos  +   1 ;
                    left  =  left  + 1 ;
                }
                 else // 将右端序列归并到temp数组中
                {
                    temp[tmp_pos]  =  myArray[mid];
                    tmp_pos  =  tmp_pos  +   1 ;
                    mid  =  mid  +   1 ;
                }
            }

             while  (left  <=  left_end)  // 拷贝左边剩余的数据到temp数组中
            {
                temp[tmp_pos]  =  myArray[left];
                left  =  left  +   1 ;
                tmp_pos  =  tmp_pos  +   1 ;
            }
             while  (mid  <=  right)  // 拷贝右边剩余的数据到temp数组中
            {
                temp[tmp_pos]  =  myArray[mid];
                mid  =  mid  +   1 ;
                tmp_pos  =  tmp_pos  +   1 ;
            }

             for  (i = 0 ; i  <  num_elements; i ++ )  // 将所有元素拷贝到原始数组中
            {
                myArray[right]  =  temp[right];
                right  =  right  -   1 ;
            }
        }
    }


归并排序算法是一种O(nlogn)的算法。它的最差,平均,最好时间都是O(nlogn)。但是它需要额外的存储空间,这在某些内存紧张的机器上会受到限制。

归并算法是又分割和归并两部分组成的。对于分割部分,如果我们使用二分查找的话,时间是O(logn),在最后归并的时候,时间是O(n),所以总的时间是O(nlogn)。

2 堆排序(HeapSort)

堆排序属于百万俱乐部的成员。它特别适合超大数据量(百万条记录以上)的排序。因为它并不使用递归(因为超大数据量的递归可能会导致堆栈溢出),而且它的时间也是O(nlogn)。还有它并不需要大量的额外存储空间。

堆排序的思路是:

(1)将原始未排序的数据建成一个堆。
(2)建成堆以后,最大值在堆顶,也就是第0个元素,这时候将第零个元素和最后一个元素交换。
(3)这时候将从0到倒数第二个元素的所有数据当成一个新的序列,建一个新的堆,再次交换第一个和最后一个元素,依次类推,就可以将所有元素排序完毕。

建立堆的过程如下面的图所示:


堆排序的具体算法如下:

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

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

         private   static   void  HeapSort()
        {
            BuildHeap();             // 将原始序列建成一个堆

             while  ( arraySize  >   1  )
            {
                arraySize -- ;
                Exchange (  0 , arraySize ); // 将最大值放在数组的最后
                DownHeap (  0  );   // 将序列从0到n-1看成一个新的序列,重新建立堆
            } 
        }

         private   static   void  BuildHeap()
        {
             for  ( int  v = arraySize / 2 - 1 ; v >= 0 ; v -- )
                DownHeap ( v );
        }

         // 利用向下遍历子节点建立堆
         private   static   void  DownHeap(  int  v )
        {
             int  w  =   2   *  v  +   1 ;                      //  节点w是节点v的第一个子节点

             while  (w  <  arraySize)
            {
                 if  ( w + 1   <  arraySize )         //  如果节点v下面有第二个字节点
                     if  ( myArray[w + 1 ]  >  myArray[w] ) 
                        w ++ ;                         //  将子节点w设置成节点v下面值最大的子节点

                  //  节点v已经大于子节点w,有了堆的性质,那么返回
                 if  ( myArray[v]  >=  myArray[w] ) 
                     return ;   
                
                Exchange( v, w );      //  如果不是,就交换节点v和节点w的值
                v  =  w;        
                w  =   2   *  v  +   1 ;             //  继续向下找子节点
            }
        }

         // 交换数据
         private   static   void  Exchange(  int  i,  int  j )
        {
             int  t  =  myArray[i];
            myArray[i]  =  myArray[j];
            myArray[j]  =  t;
        }
    }    

 

堆排序主要用于超大规模的数据的排序。因为它不需要额外的存储空间,也不需要大量的递归。

3 几种O(nlogn)算法的初步比较

我们可以从下表看到几种O(nlogn)算法的效率的区别。所有的数据都使用.Net的Random类产生,每种算法运行100次,时间的单位为毫秒。


500随机整数5000随机整数20000随机整数
合并排序0.31251.56257.03125
 Shell排序0.31251.256.875
堆排序0.468752.18756.71875
快速排序0.156250.6252.8125


从上表可以明显地看出,快速排序是最快的算法。这也就给了我们一个结论,对于一般的应用来说,我们总是选择快速排序作为我们的排序算法,当数据量非常大(百万数量级)我们可以使用堆排序,如果内存空间非常紧张,我们可以使用Shell排序。但是这意味着我们不得不损失速度。 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值