常用排序算法之堆排序
来源:互联网 发布:股票预测 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)
- 常用排序算法之堆排序
- Java常用排序算法之堆排序
- 常用排序算法之堆排序
- 常用排序算法之堆排序
- 常用排序算法之堆排序
- 常用排序算法之堆排序与快速排序
- 常用排序算法--堆排序
- Java常用算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之 堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 排序算法之堆排序
- 复习+学习js
- ButterKnife Zelezny配置与使用说明
- LeetCode代码分析——22. Generate Parentheses(DFS模板题)
- 排错+组合数
- 理解OAuth 2.0
- 常用排序算法之堆排序
- Populating Next Right Pointers in Each Node II
- LeetCode 7. Reverse Integer
- 我的第一个程序hello word及注释
- 常用排序算法之归并排序
- 销售员业绩管理程序设计
- linux 取文本的指定行的内容
- MySql 5.7 zip 安装
- 常用排序算法之快速排序