堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:
Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。
堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。
由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。
建堆算法(大根堆)
针对单个根节点的调整过程是将根节点与左右孩子节点比较。若不满足堆的条件,则将根节点与左右孩子的较大者进行交换,这个调整过程一直持续到所有子树均为堆或将该根节点交换到叶子为止。
该算法如下:
void HeapAdjust(int r[], int k, int nLength)
{
int i, j,temp;
i = k;
j = 2 * i + 1;// 子结点的位置=2*(父结点位置)+ 1
while (j < nLength)
{
if (j<nLength - 1 && r[j + 1]>r[j])// 得到子结点中较大的结点
j++;
if (r[i]>r[j])break;
else// 如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
{
temp = r[i];
r[i] = r[j];
r[j] = temp;
i = j;
j = 2 * i + 1;
}
}
}
从一个无序序列建堆的过程就是一个反复调整的过程。因为此序列就是一个完全二叉树的顺序序列,则所有的叶子节点都已经是堆,所以只需从第num/2个记录(即最后一个分支节点)开始,执行上述调整过程,直到根节点。
堆排序
利用大根堆堆顶记录的是最大关键字这一特性,使得每次从无序中选择最大记录变得简单。 其基本思想为:
1)将初始待排序关键字序列(R1,R2….Rn)构建成大根堆,此堆为初始的无序区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
void HeapSort(int r[], int length)
{
int tmp;
// 调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
//length/2-1是第一个非叶节点
for (int i = length / 2 - 1; i >= 0; --i)
HeapAdjust(r, i, length);
// 从第一个元素开始对序列进行调整,不断的缩小调整的范围直到最后一个元素
for (int i = 0; i < length; ++i)
{
// 把第一个元素和当前的最后一个元素交换,
// 保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
tmp = r[0];
r[0] = r[length - 1 - i];
r[length - 1 - i] = tmp;
// 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
HeapAdjust(r, 0, length - 1 - i);
}
}