开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情
9.3 堆排序
概念
大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
堆排序的基本思想是:
1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
9.3.1 选择排序
void Selection_Sort( ElementType A[],int N )
{
for( i = 0;i < N; i++ ){
MinPosition = ScanForMin( A,i,N-1);
//从A[i]到A[N-1]中找最小元,并将其位置赋给MinPosition
Swap(A[i],A[MinPosition]);//这两个元素通常情况下不是挨着的,可能跳了很远的距离做一个交换,一下子就消除掉很多逆序对
//将未排序部分的最小元换到有序部分的最后位置
//最坏情况就是每次都必须换一下,最多需要换N-1次
}
}
//想要得到更快的算法取决于这个ScanForMin( A,i,N-1),也就是如何快速找到最小元
最小堆的特点就是他的根结点一定存的是最小元
9.3.2 堆排序
算法1:
void Heap_Sort(ElementType A[],int N)
{
BuildHeap(A);//O(N)
for( i = 0;i < N;i++ )
TmpA[i] = DeleteMin(A);//把根结点弹出来,依次存到这个临时数组里面。O(logN)
for( i = 0;i < N;i++ )//O(N)
A[i] = TmpA[i];//将TmpA里面所有的元素导回A里面
}
//缺点:需要额外O(N)空间,并且复制元素需要时间
算法2:
void Heap_Sort(ElementType A[],int N )
{
for(i = N/2;i >= 0;i-- ){//BuildHeap,i对应的是根节点所在的位置,N对应的是当前这个堆里一共有多少个元素
PercDown(A,i,N);
for( i = N-1;i > 0;i--){//堆循环
Swap(&A[0],&A[i]);//DeleteMax,A[0]根节点里面存的是最大的元素,i是当前最后一个元素的下标,把根节点换到当前这个堆的最后一个元素的位置上去
PercDown(A,0,i);//调整的时候是以0为根节点,i是当前这个最大堆的元素个数
}
}
}
在堆排序中,元素下标从0开始。则对于下标为i的元素,其左、右孩子的下标分别为:2i+1, 2i+2
算法2的动态变化:
堆排序
void Swap( ElementType *a, ElementType *b )
{
ElementType t = *a; *a = *b; *b = t;
}
void PercDown( ElementType A[], int p, int N )
{ /* 改编代码4.24的PercDown( MaxHeap H, int p ) */
/* 将N个元素的数组中以A[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = A[p]; /* 取出根结点存放的值 */
for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
Child = Parent * 2 + 1;
if( (Child!=N-1) && (A[Child]<A[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= A[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
A[Parent] = A[Child];
}
A[Parent] = X;
}
void HeapSort( ElementType A[], int N )
{ /* 堆排序 */
int i;
for ( i=N/2-1; i>=0; i-- )/* 建立最大堆 */
PercDown( A, i, N );
for ( i=N-1; i>0; i-- ) {
/* 删除最大堆顶 */
Swap( &A[0], &A[i] ); /* 见代码7.1 */
PercDown( A, 0, i );
}