堆操作
堆是完全二叉树,因此用数组表示比较方便,i为当前结点,则 2*i(若存在) 为i的左孩子,2*i+1(若存在) 为i的右孩子。
堆中每个结点的值都不小于左右孩子的值(大顶堆),或都不大于左右孩子的值(小顶堆),所以每个结点都是以其为根节点的子树的最大(小)值。
int heap[maxn], n; // 定义堆数组以及元素个数,注意完全二叉树数组第一个元素的下标必须为1
为了使每个结点都放在正确的地方,以大顶堆为例,需要将其与左右子结点进行比较,并与其中较大的进行交换,到达新位置后,要与新的左右子结点进行比较,重复以上步骤,直到该结点比它所有子结点都大或不存在子结点。这个过程是从当前结点开始,不断向下调整的,因此有以下代码:
/* 向下调整 */
void downAdjust(int i, int end) // i为当前结点,end为堆最后一个结点
{
int j = 2 * i; // 左子结点
while (j <= end)
{
if (j + 1 <= end && heap[j + 1] > heap[j]) // 先比较左右子结点
j = j + 1;
if (heap[i] < heap[j]) // 根结点小于子结点,则向下移动
{
swap(heap[i], heap[j]);
i = j;
j = 2 * i;
}
else // 已经比所有子结点大或没有子结点
break;
}
}
在向下调整的基础上建堆:
/* 建堆 */
void createHeap()
{
for (int i = n / 2; i >= 1; i--) // 从下往上从右往左,针对所有非叶结点向下调整
downAdjust(i, n);
}
如果需要删除堆顶结点,只要用堆中最后一个结点覆盖堆顶结点,并使堆结点数-1,再对堆顶结点进行向下调整即可:
/* 删除堆顶元素 */
void deleteTop()
{
heap[1] = heap[n--]; // 覆盖并减少数量
downAdjust(1, n); // 将新的堆顶结点移动到正确的地方
}
向堆中插入结点,只要在堆末尾插入结点,并不断将插入结点与其父结点比较,若大于父结点则向上移动,直到小于等于父结点为止:
/* 向上调整 */
void upAdjust(i, begin) // begin为堆中第一个结点,i为插入结点
{
int j = i / 2; // j为父结点
while (j >= begin)
{
if (heap[i] > heap[j]) // 插入结点大于父结点
{
swap(heap[i], heap[j]);
i = j;
j = i / 2;
}
else // 插入结点小于父结点
break;
}
}
/* 插入新结点 */
void insert(int x)
{
heap[++n] = x; // 末尾插入新结点,并增加结点个数
upAdjust(n, 1);
}
利用堆的性质进行排序。从末尾向前遍历堆数组,当访问第i个结点时,将其与堆顶元素进行交换,再在[1, i - 1]范围内对堆顶元素进行向下调整(相当于提取堆顶元素后将其删除,并调整堆),直到只剩第一个元素。此时剩下的数组已经排好序。
/* 堆排序 */
void heapSort()
{
createHeap();
for (int i = n; i > 1; i--)
{
swap(heap[1], heap[i]);
downAdjust(1, i - 1);
}
}