基本概念

堆积的定义(两种形式):

内排序(六)——堆积排序_heapsort


称满足条件(1)的堆积为大顶堆积,称满足条件(2)的堆积为小顶堆积,本文使用大顶堆积。2、堆积是一棵完全二叉树,二叉树中任何一个分支结点的值都大于或者等于它的孩子结点的值,并且每一棵子树也满足堆积的特性,如下图:

内排序(六)——堆积排序_堆积排序_02

堆积有如下特点:序列的第一个元素或者二叉树的根结点的值最大

核心思想和步骤

堆积排序的核心思想如下:
第 i 趟排序将序列的前 n-i+1 个元素组成的子序列 为一个堆积,然后将堆积的第一
个元素与堆积的最后那个元素交换位置。

堆积排序的步骤如下:
1、将原始序列转换为第一个堆积。(建初始堆积);
2、将堆积的第一个元素与堆积的最后那个元素交换位置(即去掉最大值元素);
3、将“去掉”最大值元素后剩下的元素组成的子序列重新转换一个新的堆积;
4、重复上述过程的第2至第3步n-1次;

堆积排序的完整过程如下图所示:

内排序(六)——堆积排序_结点_03


堆积排序的过程中有一个非常重要的步骤就是将一个序列转换成堆,这个该如何完成呢?

我们知道,堆(二叉堆)可以视为一棵完全二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素,同时对于i个元素,它的左右子节点在下标以0开始的数组中的位置分别为:2*i+12*i+2

建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那么调堆的过程如下图,数组下标从0开始,A[3] = 5开始,分别对5,4,3,1进行调整。分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的一个交换位置,在图1中是A[7] > A[3] > A[8],所以A[3]与A[7]对换,从图1.1转到图1.2。

内排序(六)——堆积排序_heapsort_04


堆排序的动态图如下:

内排序(六)——堆积排序_堆积排序_05

算法实现

调整堆子算法的C语言实现如下:

void ADJUST(keytype K[ ],int i,int n)
{
int j;
keytype temp;
temp=K[i];
j=2*i;
while(j<=n){
if(j<n && K[j]<K[j+1])
j++;
if(temp>=K[j])
break;
K[j/2]=K[j];
j=2*j;
}
K[j/2]=temp;
}

该子算法的功能是将以编号为 i 的结点作为根的子树调整为一个堆积;
其中,
K :序列;
i :被调整的二叉树的根的序号;
n :被调整的二叉树的结点数目。

堆积排序的完整算法的C语言实现如下:

void HEAPSORT(keytype K[ ],int n)
{
int i,
keytype temp;
for(i=n/2;i>=1;i– ) //建立初始堆积
ADJUST(K,i,n);
for(i=n–1;i>=1;i– ){ //具体排序,共n-1趟
temp=K[i+1];
K[i+1]=K[1];
K[1]=temp;
ADJUST(K,1,i);
}
}

算法分析

堆积排序的时间复杂度为O(nlog2n)

本文部分参考自:​​堆排序算法详解​