1. 堆
堆是一种特殊的二叉树结构,其分为最大堆和最小堆两种。对于最大堆来说,父结点的值一定会比子结点大,最小堆类似。下图为一个简单的最大堆示例,父结点30要比子结点12和22要大,至于两个子结点之间的大小关系则没有要求。
如果用一个数组来存储一个堆,那么我们需要知道每个元素对应的结点的父子结点分别是数组中的哪一个元素。用数组表示的堆的结构以及各个结点对应的元素下标如下图所示。
可以归纳出,下标为i的元素对应的结点的父结点的下标为 (i-1)/2 下取整,左孩子结点的下标为 i*2+1,有孩子结点的下标为 i*2+2。可以据此宏定义一套求下标为i的元素的父子结点的下标的方法PARENT(i),LEFT(i)和RIGHT(i)。根据以上分析,可以知道一个数组中,假设其长度为len,那么前 len/2 个元素均不是叶子节点。堆的根结点就是数组的最大值对应的结点。最小堆类似。
维护一个结点的最小堆性质的代码如下所示。
void minimize(int *arr, int len, int i)
{
if (i > len / 2)
{
return;
}
int left = LEFT(i);
int right = RIGHT(i);
int min = arr[i];
int minOne = i;
if (left < len && min > arr[left])
{
min = arr[left];
minOne = left;
}
if (right < len && min > arr[right])
{
min = arr[right];
minOne = right;
}
if (minOne != i)
{
int temp = arr[i];
arr[i] = arr[minOne];
arr[minOne] = temp;
}
}
上述代码将指定结点作为父结点,从改结点及其孩子结点中找到最小的一个并交换到该结点中。
2. 堆排序
堆排序的工作过程就是反复调用minimize方法找到最小值放到数组的第一个位置,然后对剩余的元素继续进行上述过程直到结束。
void heapSort(int *arr, int n)
{
if (n <= 1)
{
return;
}
for (int i = n / 2; i >= 0; i--)
{
minimize(arr, n, i);
}
heapSort(++arr, n - 1);
}
上面说到过,一个数组的前面 len/2 个元素都是非叶子结点,而最小堆性质肯定只是针对非叶子结点而言的,所以heapSort过程只对数组的前 len/2 个元素调用minimize方法。其实这个过程就是自底向上,在维护最小堆性质的同时找到数组中的最小元素。
堆排序的时间复杂度为O(nlg(n))。