一、向下调整算法
建堆之前需要先实现向下排序算法,向下排序算法的前提是:左右子树均为小堆(或大堆)。调整步骤(以大堆为例):比较左右孩子的大小,得到两个孩子的较大值与父亲比较。如果大于父亲,就将子节点与父节点数据交换,子节点作为父节点继续向下调整,直到当前父节点已经没有子节点时停止调整;如果小于或等于父亲,说明已经是大堆,停止调整。按照该步骤大致作图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d0e76747a70a5b82319e52f42ae02c08.png)
代码实现:
//以大堆为例
void Swap(int* e1, int* e2)
{
int tmp = *e1;
*e1 = *e2;
*e2 = tmp;
}
void adjustDown(int* arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//右孩子大于左孩子
if (child + 1 < n && arr[child + 1] > arr[child])
child++;
//孩子小于等于父亲说明当前数组已为大堆
if (arr[child] <= arr[parent])
break;
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
}
二、建堆
上面说道向下调整算法的前提是左右子树均为小堆(或大堆),所以对于任意的完全二叉树,向下调整算法不适用。要把任意的完全二叉树建成堆,需要从树的最后一个度不为0的节点开始向下调整,调整一个节点后继续让前一个节点向下调整,直到根节点向下调整完成后,堆就建好了。
代码实现:
/*
其中n代表数组长度(完全二叉树节点个数)
对于完全二叉树 最后一个度不为0的节点即为最后一个节点对应的父节点
无论左孩子还是右孩子 都有parent == (child - 1) / 2
其中child为子节点下标
*/
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
adjustDown(arr, n, i);
}
三、堆排序
建好堆之后,就可以进行堆排序了。以大堆为例,每次将堆顶的元素(最大的元素)与堆底最后一个节点元素进行交换(最大的元素放到最后),再把最后一个节点排除在外,从堆顶进行向下调整,使剩余的节点又形成大堆。重复上述操作,直到堆中除堆顶节点外,其他节点都被排除时,排序完成。一轮排序的流程如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/4558bdf7bafa924f59066e57110125ae.png)
代码实现:
void heapSort(int* arr, int n)
{
int end = n - 1;//最后一个节点的下标
while (end > 0)
{
Swap(&arr[0], &arr[end]);
adjustDown(arr, end, 0);
end--;
}
}