堆
堆是一棵完全二叉树,每个节点的值都不小于(不大于)其左右孩子结点的值。
如果父亲结点的值大于等于孩子结点的值,那么称这样的堆为大顶堆,这时每个节点的值都是以它为根结点的子树的最大值;反之为小顶堆。
堆的实现
由于堆是一棵完全二叉树,所以我们可以用数组来实现一棵完全二叉树,也就能实现堆。
完全二叉树:
除了最下面一层之外,其余层的结点个数都达到了当层能够达到的最大结点数且最下面一层从左至右连续存在若干结点
表示方法:<br.>
可以用数组来表示完全二叉树:对于下标为 x 的结点,其左结点的下标为 2x,右结点为 2x+1
建堆过程
对于一个大小为 n 的堆,我们使用向下调整来进行堆结构的完善
// 对 heap 数组在 [low, high] 范围进行向下调整
// 其中 low 为欲调整结点的数组下标,
// high 一般为堆的最后一个元素的数组下标
void downAdjust(int low, int high) {
int i = low, j = i * 2; // i为调整目标, j 为左孩子
while (j <= high) // 存在孩子结点
{ // 右孩子存在且值大于左孩子
if(j + 1 <= high && heap[j+1] > heap[j]) {
j = j + 1;
}
// 如果孩子中最大的权值比欲调整结点 i 大
if(heap[j] > heap[i]) {
// 交换最大权值的孩子与欲调整结点 i
swap(heap[i], heap[j]);
// 保持 i 为欲调整结点,j 为 i 的孩子
i = j;
j = 2 * i;
}else {
break; // 孩子都比欲调整结点 i 小,调整结束
}
}
}
/*
如果有 n 个元素,那么非叶子结点从 1 开始一直到 n/2
从 n/2 倒着枚举结点。
*/
void createHeap() {
for (int i = n/2; i >= 1; i--)
{
downAdjust(i, n);
}
}
删除堆顶元素
删除堆顶元素,并让其仍然保持堆的结构,只需要让最后一个元素覆盖堆顶,然后对根结点进行调整
int popTop() {
heap[1] = heap[n--];
downAdjust(1, n);
}
向堆中增加元素
往堆中添加一个元素,把想要添加的元素放在数组最后,然后进行向上调整操作
向上调整总是与父节点比较,如果权值比父亲节点大
那么交换其与父亲结点,直到到达堆顶或者父亲结点的权值更大
void upAdjust(int low, int high) {
int i = high, j = high / 2; // j 为父亲结点
while (j >= low)
{ // 如果父亲结点的权值比当前权值小,那么交换
if(heap[j] < heap[i]) {
swap(heap[i], heap[j]);
i = j;
j = i / 2;
}else {
break;
}
}
}
堆排序
堆排序是指使用一个序列进行排序。此处讨论递增排序的情况。
考虑对一个堆来说,堆顶元素是最大的,因此在建堆完毕后,堆排序的只管思路就是取出堆顶元素,然后将堆的最后一个元素替换至堆顶,在进行一次针对堆顶元素的向下调整,直到堆中只有一个元素。
void heapSort(){
createHeap(); // 建堆
for (int i = n; i > 1; i--)
{
swap(heap[i], heap[1]);
downAdjust(1, i-1); // 调整堆顶
}
}