堆的概念
通俗的说就是小顶堆本身的值小于或等于自己的任一个孩子节点(如果有孩子节点的话),这样看来,小顶堆中根节点的值是所有元素中最小的。大顶堆则刚好相反。
堆节点的访问
通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:
- 父节点i的左子节点在位置(2*i+1);
- 父节点i的右子节点在位置(2*i+2);
- 子节点i的父节点在位置floor((i-1)/2);
以下为上图中的大顶堆在一维数组中的的存储情况。
80 | 60 | 16 | 50 | 45 | 10 | 15 | 30 | 40 | 20 |
---|
堆排序基本思想
根据堆的定义可以知道k[0]就是所有元素中最大的一个,把它与k[n-1]交换,变成下表的形式:
20 | 60 | 16 | 50 | 45 | 10 | 15 | 30 | 40 | 80 |
---|
现在k[n-1]是最大的,它的位置就已经定下来了,再将K[0…n-2]调整为新的堆,如下:
60 | 50 | 16 | 40 | 45 | 10 | 15 | 30 | 20 | 80 |
---|
接着把k[0]与k[n-2]交换,再将K[0…n-3]调整为新的堆。
重复这样的操作,直到k[0]与k[1]交换,完成排序。
现在就有两个问题,
第一个就是怎么样将一个初始序列建成堆的形式。
第二个就是删除根节点后,怎么将剩下的元素调整为新的堆。
初建堆
假设有数组如下:
1 | 3 | 4 | 5 | 7 | 2 | 6 | 8 | 0 |
---|
将5和8,4和6交换位置之后,调整3的位置。
最后调整1的位置
这样就完成了建造大顶堆的过程。
删除根节点
以上为删除根节点的示意图,再将根节点删除之后,需要把堆的最后一个节点升为新的根节点,然后再对根节点进行位置调整,调整步骤同前面讲的步骤一样。
这也为什么进行排序时要将第一个元素和最后一个元素交换位置,这正好符合删除根节点的过程,这样就不必再重新创建一个数组来依次保存删除的元素。
c++实现
template<typename Type>
void max_heapify(vector<Type> & a, int start, int end)
{
int dad = start;
int son = dad * 2 + 1;
while (son < end)
{
if (son + 1 < end && a[son] < a[son + 1]) //先比较两个子节点的大小,选择较大的
{
son++;
}
if (a[dad] > a[son]) //如果父节点大于较大的子节点,则不需要调整
{
return;
}
else
{ //否则交换位置,再继续子节点与孙节点的比较
swap(a[dad], a[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
template<typename Type>
void heap_sort(vector<Type> & a, int n)
{
//初始化,i从最后一个非叶子节点开始
for (int i = n / 2 - 1; i >= 0; --i)
{//建造大顶堆
max_heapify(a, i, n);
}
for (int i = n - 1; i > 0; --i)
{//先将第一个元素和已经排好元素的前一位做交换(第一次交换时,和最后一个元素交换,交换完毕后,最后一位就是已排好的元素),
//再重新调整(刚调整的元素之前的元素),直到排序完毕。
swap(a[0], a[i]);
max_heapify(a, 0, i);
}
}