排序算法从入门到精通之八–堆排序
1. 堆排序简介
本文要介绍另一种排序算法,即堆排序。像归并排序(而不像插入顺序)一样,堆排序的运行时间为O(n*ln(n)).像插入排序(而不像归并排序)一样,它是一种原地排序算法,在任何时候,数组中只有常数个元素存储在输入数组以外。这个堆排序就把归并排序和插入排序两种排序算法的优点结合起来。
2. 堆的结构与表示
堆排序使用一个数组表示一个完全二叉树。这里,一个结点数为N的完全二叉树,其非叶子结点数为N/2,叶子结点数为N-(N/2)。
算法导论的伪代码中,数组的下标是从1开始的。若A[]表示一个数组,整个树的根结点放在A[1]。对于某个下标为i的结点,则这个结点的的双亲结点,子结点的下标具有如下关系
双亲结点的下标,PARENT(i)=i/2
左子结点的下标,LEFT(i)=i2
右子结点的下标,RIGHT(i)=i2+1
C语言的下标从0开始,若采用上述表示法,则浪费一个元素的空间。这与这个系列的排序函数的接口不统一。故我们采用下述二叉树的表示法。
1.整个树的根结点放在A[0]
2. 对于下标为i的结点,其双亲结点和子结点的下标之间的关系如下
双亲结点的下标,PARENT(i)=(i-1)/2.
左子结点的下标,LEFT(i)=i2+1
右子结点的下标,RIGHT(i)=i2+2
3. 若这个数组具有N个元素,则最后一个元素的下标是N-1,则其双亲结点的下标为(N-1-1)/2=N/2-1. 故最后一个非叶子结点的下标是N/2-1
3. 堆的性质和堆化
堆分为大顶堆和小顶堆。
对于大顶堆,堆中的每一个非叶子结点,其左子结点的和右子结点的值须不大于自身。
对于小顶堆,堆中的每一个非叶子结点,其左子结点的和右子结点的值须不小于自身。
容易看出,若堆中元素的值各不相同,则大顶堆的根结点的值是整个数组中最大的,而小顶堆的根结点的值是整个数组中最小的。
若数组中的元素不满足堆的性质,则需将某些结点与子结点交换,以满足堆的性质,这个过程叫做堆化。
4. 堆排序算法
堆排序算法分为两步。
第一步,将整个数组堆化,若需增序排列,构建大顶堆,否则,构建小顶堆.
第二步,不断删除堆顶元素,放在堆的后面,并对剩下的元素堆化。如此往复,直至堆中仅为一个元素。
这一步可以这样理解,对于长度为N的数组,分为两部分,前半部分是一个大顶堆(或小顶堆),后半部分是一个有序数组.
开始时,前半部分长度为N,后半部分长度为0,随着排序过程的进行,前半部分(堆)的长度越来越短,而后半部分则越来越长.
当堆中元素仅为一个元素时,堆中那个元素必小于或者等于后半部分第一个元素,这样,整个数组都变成一个有序数组.
#include <stdio.h>
#include <stdlib.h>
#include "sorts.h"
#define PARENT(i) ((i)-1)/2)
#define LEFT(i) ((i)*2+1)
#define RIGHT(i) ((i)*2+2)
#define SWAP(T,a,b) {T t=a; a=b; b=t; }
//从i结点开始,做maxHeapify,这是一个递归函数
static void maxHeapify(ELE_TYPE A[],int i,int size)
{
int l = LEFT(i); // i的左子结点
int r = RIGHT(i); // i的右子结点
int maxNodeIdx = i; // 值最大的那个结点的下标,初化为i
if ( l<size && A[l] > A[maxNodeIdx])
maxNodeIdx = l;
if ( r<size && A[r] > A[maxNodeIdx])
maxNodeIdx = r;
if (maxNodeIdx != i) // 最大值不是当前结点,交换之
{
SWAP(ELE_TYPE,A[i],A[maxNodeIdx]);
maxHeapify(A, maxNodeIdx, size); //继续对子结点做堆化操作,保证子树也是大顶堆
}
}
// 使长度为n的数组A变为一个大顶堆。其时间复杂度为O(n)
static void buildMaxHeap(ELE_TYPE A[], int n)
{
// 从最后一个非叶子节点开始,对每一个非叶子节点做maxHeapify
int lastNonLeafNode=n/2-1;
for (int i = lastNonLeafNode; i >= 0; i--)
maxHeapify(A, i, n);
}
//堆排序算法
void heap_sort(ELE_TYPE arr[], int len)
{
if (len<2)
return ;
buildMaxHeap(arr,len); //将整个数组变成一个大顶堆
int heapSize=len;
do
{
// 将根节点(最大值)与堆的最后一个元素arr[heapSize-1]交换
SWAP(ELE_TYPE,arr[0],arr[heapSize-1]);
heapSize--; // 堆(待排序数组)长度减一
// 此时,arr[heapSize]变成有序数的的第一个元素
maxHeapify(arr, 0, heapSize); //保持堆结构
}while (heapSize>1);
}
void test_heap_sort()
{
ELE_TYPE arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
int len;
printf("Original data are:");
len = (int) sizeof(arr) / sizeof(arr[0]);
print_array(arr, len);
heap_sort(arr, len);
printf("The data after sorted are:");
print_array(arr, len);
}