常用排序算法之堆排序

来源:互联网 发布:股票预测 python 编辑:程序博客网 时间:2024/05/16 18:28

堆排序

  • 基本思想:

要进行堆排序,首先要建立N个元素的二叉堆,建立好二叉堆后,我们执行N次DeleteMin操作。按照顺序,最小的元素最先离开该堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,即可得到N个元素的排序。

涉及到堆,就一定要保持堆的性质:(结构性质堆序性质

结构性质:堆是一颗被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树(除最后一层外,每一层的节点数都达到最大值,在最后一层上只缺少右边的若干节点)。

堆序性质:在一个堆中,父节点中的关键字大于等于子节点中的关键字。

这里,我们需要使用一个附加数组,最后将第二个数组中的元素拷贝回第一个数组完成排序。这使得空间需求增加了一倍,而且拷贝也花费了O(n)的时间。我们可以利用以下事实来避免使用第二个数组。每次DeleteMin后堆缩小了一,我们可以用堆中最后的单元来存储刚刚被删去的元素。这样最后数组中就是按照从大到小顺序排列的元素。如果我们想让元素按照从小到大的顺序排列可以每次进行DeleteMax操作。

实例:现有七个数:53、97、26、41、58、31、59。对这七个数进行堆排序,因此首先要开始建立堆,建立堆的过程就是不断的将最大元上滤,将空穴下滤的过程。最终使得数组满足堆的性质。堆序性质虽然使得序列在一定程度上有了顺序,但是由于堆序性质没有规定左右子树的大小顺序,所以我们进行堆排序还是有必要的。

建立好堆后,我们开始进行排序,每次删除最大的元素后,堆的大小减小了一个单位,我们将最大元素放在堆中被减去的那个单元中,最后再将堆序性质进行恢复。

  • 程序实现

#include<iostream>using namespace std;typedef int ElementType;#define LeftChild( i )  ( 2 * ( i ) + 1 )//因为i从0开始,所以左孩子的2*i+1void print(int a[], int n ,int i){      cout<<"i="<<i <<":";      for(int j= 0; j<n; j++)    {          cout<<a[j] <<" ";      }      cout<<endl;  }  void Swap( ElementType *Lhs, ElementType *Rhs ){    ElementType Tmp = *Lhs;    *Lhs = *Rhs;    *Rhs = Tmp;}void PercDown( ElementType A[ ], int i, int N ){    int Child;    ElementType Tmp;    for( Tmp = A[ i ]; LeftChild( i ) < N; i = Child )//    {        Child = LeftChild( i );        if( Child != N - 1 && A[ Child + 1 ] > A[ Child ] )            Child++;        if( Tmp < A[ Child ] )            A[ i ] = A[ Child ];//当将        else            break;    }    A[ i ] =Tmp;}void Heapsort( ElementType A[ ], int N ){    int i;    cout<<"建立堆:"<<endl;//n 个结点的完全二叉树,则最后一个拥有子树的节点是n/2(向下取整)。因此我们从i=n/2开始到i=0结束对序列进行建堆操作,也可以从i=0到i=n或者从i=n到i=0。只是增加了不必要的比较操作。     for( i = N / 2; i >= 0; i-- )  /* BuildHeap */    {        PercDown( A, i, N );        print(A, N ,i);    }    cout<<"堆排序:"<<endl;    for( i = N - 1; i > 0; i-- )//数组从0开始,最后一个元素的下标是N-1    {        Swap( &A[ 0 ], &A[ i ] );  /* DeleteMax */        PercDown( A, 0, i );        print(A, N ,i);    }}int main(){    int a[7]={53,97,26,41,58,31,59};        Heapsort(a,7);    system("pause");    return 0;}

  • 程序实现分析:

    根据堆序性质,根上的元素的值是最大的(即A[0]是最大的),我们每次将A[0]与最后的元素交换,交换后将堆的大小减一(相当于将最大的元素删除了),然后再将少了一个元素的堆恢复堆序性质。

     PercDown程序主要对序列进行操作,使得序列满足堆的性质。每次找出子节点中最大的节点与父节点进行交换。
     初始是不满足堆序性质的二叉树,我们不断的下滤,使得序列满足堆序性质,完成堆的建立。
     先将 A[0]存在Tmp中,相当于将根变成空穴,
     1: i=3,LeftChild=7=N;结束
     2: i=2,LeftChild=5;A[6]>A[5];A[6]>A[2],将A[6]与A[2]交换(右儿子大,[Child = LeftChild( i ),Child++].与右儿子交换)
         i=LeftChild+1=6;LeftChild=13>N;结束
     3: i=1,LeftChild=3;A[4]>A[3];A[4](右儿子大)
         i=LeftChild+1=4;LeftChild=9>N;结束
     4: i=0,LeftChild=1;A[1]>A[2];A[1]>A[0],将A[1]与A[0]交换(与左二子交换)
         i=LeftChild=1;LeftChild=3<N;A[4]>A[3];A[4]>A[1],将A[4]与A[1]交换(与右儿子交换)
         i=LeftChild+1=4;LeftChild=9>N;结束
    (为什么不考虑根的另一个分支?因为每次上滤过程最大元素只改变了根的一个分支,另一个分支没有改变任然保持了堆序性质,所以不用考虑。)
     [   
      堆的建立过程可以概括为以下几步:
      a,将根结点与左、右子树中较大元素的进行交换。
      b,若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法a。
      c,若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法a。
      d,继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

    ]

    堆排序过程示例:

   初始状态:

        


   建堆过程

   1、

         

   2、

         

   3、

          

   4、

           

   5、

           

   建立好了二叉堆后,下来就可以开始进行排序了。堆排序过程,删除最大元素很容易,麻烦的是将删除了最大元素的堆恢复堆序性质。

 堆中元素数N=6

1:i=N-1=6;(注意这里的i与PercDown中的i不同)将A[0]与A[6]交换,此时,堆中仅有6个元素,N=6;现在我们再将这六个元素恢复堆序性质。

PercDown( A, 0, 6 ):

  i=0,LeftChild=1;A[2]>A[1];A[2]>A[0],将A[2]与A[0]交换(与右儿子交换)

  i=LeftChild+1=2;LeftChild=5<N;Child=LeftChild=5=N-1;A[5]>A[2],将A[5]与A[2]交换(与左儿子交换)<a[2],不交换< span="" style="box-sizing: border-box; margin-bottom: 0px;">

  i=LeftChild=5;LeftChild=11<N;结束

           

  当前堆中只剩6个元素,现在我们恢复当前堆的堆序性质。

                                     

 

 堆中元素数N=5

2:i–;i=5将A[0]与A[5]交换,此时,堆中仅有5个元素,N=5;现在我们再将这五个元素恢复堆序性质。

PercDown( A, 0, 5 ):

  i=0,LeftChild=1;A[1]>A[2];A[1]>A[0],将A[1]与A[0]交换(与左儿子交换)

  i=LeftChild=1;LeftChild=3<N;A[4]>A[3],A[4]>A[1],将A[4]与A[1]交换(与右儿子交换)<a[2],不交换< span="" style="box-sizing: border-box; margin-bottom: 0px;">

  i=LeftChild+1=4;LeftChild=9>N;结束

                               

                       

                        

 堆中元素数N=4

3:i–;i=4将A[0]与A[4]交换,此时,堆中仅有4个元素,N=4;现在我们再将这四个元素恢复堆序性质。

PercDown( A, 0, 4 ):

  i=0,LeftChild=1;A[1]>A[2];A[1]>A[0],将A[1]与A[0]交换(与左儿子交换)

  i=LeftChild=1;LeftChild=3<N;LeftChild=3=N-1;A[3]>A[1],将A[3]与A[1]交换(与左儿子交换)<a[2],不交换< span="" style="box-sizing: border-box; margin-bottom: 0px;">

  i=LeftChild=3;LeftChild=7>N;结束

                          

                           

  

 堆中元素个数N=3

4:i–;i=3将A[0]与A[3]交换,此时,堆中仅有3个元素,N=3;现在我们再将这三个元素恢复堆序性质。

PercDown( A, 0, 3 ):

  i=0,LeftChild=1;A[1]>A[2];A[1]>A[0],将A[1]与A[0]交换(与左儿子交换)

  i=LeftChild=1;LeftChild=3=N;结束。

               

               

 堆中元素个数N=2

5:i–;i=2将A[0]与A[2]交换,此时,堆中仅有2个元素,N=2;现在我们再将这二个元素恢复堆序性质。

PercDown( A, 0, 2 ):

  i=0,LeftChild=1;Child=LeftChild=1=N-1;A[1]

  i=LeftChild=1;LeftChild=3>N;结束。

              

               

  堆中元素个数N=1

6:i–;i=1将A[0]与A[1]交换,此时,堆中仅有1个元素,N=1;现在我们再将这一个元素恢复堆序性质。

PercDown( A, 0, 1 ):

  i=0,LeftChild=1;LeftChild=N,结束。     

  最终结果即为下图:

7:i–;i=0;结束。

最终数组中的元素以递增的顺序被排列。

  • 堆排序的时间复杂度为O(NlogN)
0 0