基本概念:

1、完全二叉树:若二叉树的深度为h,则除第h层外,其他层的结点全部达到最大值,且第h层的所有结点都集中在左子树。

2、满二叉树:满二叉树是一种特殊的的完全二叉树,所有层的结点都是最大值。

什么是堆?

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

       ![](https://i-blog.csdnimg.cn/blog_migrate/e4ecb1a8ef4456d88da12d8d472dcb81.png)![](https://i-blog.csdnimg.cn/blog_migrate/5ef19d242f141a1218580547e52f404d.png)

堆是非线性数据结构,相当于一维数组,有两个直接后继。

堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。

(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)

若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
注意: 在二叉树中,若当前节点的下标为 i, 则其父节点的下标为 i/2,其左子节点的下标为 i*2,其右子节点的下标为i*2+1;

堆的插入:

每次插入都是将先将新数据放在数组最后,由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中——这就类似于直接插入排序中将一个数据并入到有序区间中。

我们通过一个插入例子来看看插入操作的细节。我们将数字 16 插入到这个堆中:

堆的数组是: [ 10, 7, 2, 5, 1 ]

第一步是将新的元素插入到数组的尾部,数组变成:[ 10, 7, 2, 5, 1, 16 ];

相应的树变成了:

16 被添加最后一行的第一个空位。

不行的是,现在堆属性不满足,因为 216 的上面,我们需要将大的数字在上面(这是一个最大堆)

为了恢复堆属性,我们需要交换 162

现在还没有完成,因为 10 也比 16 小。我们继续交换我们的插入元素和它的父节点,直到它的父节点比它大或者我们到达树的顶部。这就是所谓的 shift-up,每一次插入操作后都需要进行。它将一个太大或者太小的数字“浮起”到树的顶部。

最后我们得到的堆:

现在每一个父节点都比它的子节点大。

堆的删除:

堆中每次都只能删除堆顶元素。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于根结点数据的“下沉”过程。

我们将这个树中的 (10) 删除:

现在顶部有一个空的节点,怎么处理?

当插入节点的时候,我们将新的值返给数组的尾部。现在我们来做相反的事情:我们取出数组中的最后一个元素,将它放到树的顶部,然后再修复堆属性。

现在来看怎么 shift-down (1)。为了保持最大堆的堆属性,我们需要树的顶部是最大的数据。现在有两个数字可用于交换 72。我们选择这两者中的较大者称为最大值放在树的顶部,所以交换 71,现在树变成了:

继续堆化直到该节点没有任何子节点或者它比两个子节点都要大为止。对于我们的堆,我们只需要再有一次交换就恢复了堆属性:

最大堆:

1,构造最大堆

原始数据为a[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7},采用顺序存储方式,对应的完全二叉树如下图所示:

基本思想
首先将每个叶子节点视为一个堆,再将每个叶子节点与其父节点一起构造成一个包含更多节点的对。所以,在构造堆的时候,首先需要找到最后一个节点的父节点,从这个节点开始构造最大堆;直到该节点前面所有分支节点都处理完毕,这样最大堆就构造完毕了。
假设树的节点个数为n,以1为下标开始编号,直到n结束。对于节点i,其父节点为i/2;左孩子节点为i*2,右孩子节点为i*2+1。最后一个节点的下标为n,其父节点的下标为n/2。
我们边针对上边数组操作如下图所示,最后一个节点为7,其父节点为16,从16这个节点开始构造最大堆;构造完毕之后,转移到下一个父节点2,直到所有父节点都构造完毕。

代码实现如下:

strcut MaxHeap
{
Etype *heap; //数据元素存放的空间,下标从1开始存数数据,下标为0的作为工作空间,存储临时数据
int HeapSize;//数据元素的个数
int MaxSize; //存放数据元素空间的大小
};
MaxHeap H;

void MaxHeapInit (MaxHeap &H)
{
for(int i = H.HeapSize/2; i>=1; i--)
{
H.heap[0] = H.heap[i];
int son = i*2;
while(son <= H.HeapSize)
{
if(son < H.HeapSize && H.heap[son] < H.heap[son+1])
son++;
if(H.heap[0] >= H.heap[son])
break;
else
{
H.heap[son/2] = H.heap[son];
son *= 2;
}
}
H.heap[son/2] = H.heap[0];
}
}

再举一个详细的实例过程:

给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。首先根据该数组元素构建一个完全二叉树,具体过程如下(从左到右,从上到下按顺序一步一步的详细过程):





2,最大堆插入节点

最大堆的插入节点的思想就是先在堆的最后添加一个节点,然后沿着堆树上升。跟最大堆的初始化过程大致相同。

void MaxHeapInsert (MaxHeap &H, EType &x)
{
if(H.HeapSize == H.MaxSize)
return false;
int i = ++H.HeapSize;
while(i!=1 && x>H.heap[i/2])
{
H.heap[i] = H.heap[i/2];
i = i/2;
}
H.heap[i] = x;
return true;
}

3,最大堆堆顶节点删除

最大堆堆顶节点删除思想如下:将堆树的最后的节点提到根结点,然后删除最大值,然后再把新的根节点放到合适的位置。

void MaxHeapDelete (MaxHeap &H, EType &x)
{
if(H.HeapSize == 0)
return false;
x = H.heap[1];
H.heap[0] = H.heap[H.HeapSize--];
int i = 1, son = i*2;

while(son <= H.HeapSize)
{
if(son <= H.HeapSize && H.heap[0] < H.heap[son+1])
son++;
if(H.heap[0] >= H.heap[son])
break;
H.heap[i] = H.heap[son];
i = son;
son = son*2;
}
H.heap[i] = H.heap[0];
return true;
}

最小堆

整体操作和最大堆类似,这里不做赘述。