堆排序
排序算法主要分三大类,交换排序有冒泡排序和快速排序,插入排序有简单插入排序和希尔排序,选择排序有堆排序和简单选择排序。其中快速排序和堆排序的时间复杂度为 o(nlogn),但这两种排序都不稳定(相等的元素在排序前后的顺序是否会发生改变)。
堆排序是一种选择排序,其原理是在序列上建立大顶堆(或者小顶堆),堆顶元素便是序列的最大值(或者最小值),将堆顶的元素放到堆尾部,再调整除堆尾部元素的其他元素,将堆重新调整成大顶堆,取出堆顶元素放在新的堆尾部,依次调整堆顶,取出最大元素,完成排序过程。
以降序的堆排序为例。
- 首先,构造小顶堆。
从最后一个非叶子节点开始,从左至右,从下至上进行调整,将该非叶子节点和其子节点中数值较小的调整至非叶子节点位置。依次寻找剩余的非叶子节点进行调整。
调整某一个非叶子节点的代码如下所示:
//堆中非叶子节点调整
void HeapAdjust(int data[],int length,int k)
{
int tmp=data[k];
int i=2*k+1;
while(i<length)
{
//选择节点 k 的两个自节点中值较小的
if(i+1<length && data[i]>data[i+1])
++i;
//比较节点 k 与其子节点中值较小的数值
if(tmp<data[i])
break;
//将较小的子节点的值交换到节点 k 中
data[k]=data[i];
//下一轮循环中继续调整节点k的子节点的子节点
k=i;
i=2*k+1;
}
//此时 k 是最近的一个调整过的节点
//可能是非叶子节点(若非叶子节点的子节点都大于它,无需调整),也可能是叶子节点(非叶子节点大于其子节点,调整过)
data[k]=tmp;
}
其中 length 是堆中元素数量, k 是待调整的非叶子节点索引,i 是节点 k 中值较小的子节点,tmp 记录了节点 k 的值。判断节点 k 与其较小子节点的大小关系,若需要调整,则还需要调整该子节点与其子节点的位置,在下一轮循环中完成,最后将最开始的节点 k 的值交换到最后一次调整的位置。
2. 排序,将堆顶元素移动到堆尾部,将堆的长度减一,重新调整堆顶,继续移动堆顶元素到尾部,堆长度减一,重新调整堆顶,循环直至堆的长度减小到 0 。代码如下所示:
//堆排序 单调递减排列
void HeapSort(int data[],int length)
{
if(data == NULL || length <= 0)
return;
//建立小顶堆
for(int i=length/2-1;i>=0;i--)
{
HeapAdjust(data,length,i);
}
//将堆顶元素移动到堆尾部,重新调整堆顶
for(int i=length-1;i>0;i--)
{
swap(data[0],data[i]);
HeapAdjust(data,i,0);
}
}