一、什么是堆?
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
-
堆中某个节点的值总是不大于或不小于其父节点的值;
-
堆总是一棵完全二叉树。
简单点说堆就是满足特定条件的完全二叉树,这里的“特定条件”分两种:即要么这棵树所有的节点都不大于(小于等于)其根节点(如果有)的值,如下图所示就称为最大堆。
要么这棵树所有的节点都不小于(大于等于)其根节点的值,如下图所示的称为最小堆。
一句话,对于一颗完全二叉树,根节点总最大时叫最大堆,根节点总最小时叫最小堆。
二、如何构造一个堆
以构建最大堆为例,为了方便起见,不妨用一个一维数组来表示一颗完全二叉树,例如:
int heap[] = {
16,4,10,14,7,9,3,2,8,1 };
已知heap是一个完全二叉树,自然能够画出它的等效结构:
显然heap不是最大堆,为了使其满足最大堆结构,我们需要对二叉树的结构进行调整。
为了保证每次调整都向最大堆的目标更进一步,我们应当对整棵树调整。我们采取自下而上的策略调整二叉树(为什么不是自上而下呢?)。
具体思路是:1.对于除了叶子节点外的任一节点A,假设其两个孩子节点分别为B、C,如果A的值不是最大的,则将节点A和最大的那个节点(B或者C)交换。考虑到A节点被下放之后可能会影响已经调整好的子树(如果A不是叶子结点),我们应当做好后续收尾工作——对A的当前位置重复上一步的操作(即递归),直到A已经是最大数值时调整完毕。
- 第一个小目标:实现一颗最小子树的调整。需要注意我们已经选择了自下而上的策略,当对根节点A进行操作时,它的两个左右子树都已经满足最大堆的条件,需要做的就是将A放到合适的位置以及处理好一旦A下沉给底层带来的那些新问题。
void MaxHeapIFY(int *heap,int index,int heap_size)
{
//寻找当前节点和其左右孩子中的最大值并交换,因为实际是自下而上操作,所以默认当前节点的左右子树都已是最大堆
int left = 2 * index + 1;
int right = left + 1;
int largest = index;
if (left < heap_size && heap[left]>heap[index]) // not out of index
largest = left;
if (right<heap_size && heap[right]>heap[largest])
largest = right;
if (largest != index) // 交换最大节点
{
int temp = heap[largest];
heap[largest] = heap[index];
heap[index] = temp;
//如果当前堆发生改变,则递归地操作后续堆以适应最大堆结构
MaxHeapIFY(heap, largest, heap_size);
}
}
2.自底向下调用第一步的函数对整棵树进行调整,这里需要注意的细节是叶子结点在数组中的下标为heap_size/2-1
void BuildMaxHeap(int* heap,int heap_size)
{
for (int i = (heap_size) / 2 - 1; i >= 0; i--)
{
MaxHeapIFY