1、堆的概念
如果有一个关键码的集合 K={K0,K1,K2,…,Kn-1},把所有完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i +1 且 Ki <= K2i + 2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
-
堆中每个节点的值总是不大于或不小于其父节点的值;
-
堆是一棵完全二叉树
2、堆的存储方式
二叉堆是一棵完全二叉树,所以很容易用数组表示,因为一棵高度为 h 的完全二叉树有 2h到 2(h+1)-1个节点,用数组存放一个二叉堆就不会太浪费空间,而且一旦知道高度,就可以知道节点数的范围。而且非常节省存储空间。因为不需要存储左右子节点的指针,单纯通过数组的下标就可以找到一个节点的左右子节点和父节点。
假设i为节点在数组中的下标,则有:
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子
3、堆的创建
将数组"原地"的建成一个堆,不借助辅助空间
方式一:利用在堆中插入元素的思路,尽管数组中包含 n 个元素,可以假设起初堆中只包含一个数据,就是下标为 1 的数据。然后调用插入操纵,将下标从 2 大牌 n 的数据依次插入到堆中,这样就将包含 n 个数据的数组,组织成堆。
方式二:对一组普通的序列向上调整或者向下调整从而形成堆,这个过程可以称为“堆化”(堆化就是顺着节点所在路径,向上或向下对比然后交换)。
对以下序列{7 5 19 8 4 1 20 13 16} 画图理解向上调整的过程
代码实现:
private static void buildHeap(int[] a, int n) {
for (int i = n/2; i >= 1; --i) {
heapify(a, n, i);
}
}
private static void heapify(int[] a, int n, int i) {
while (true) {
int maxPos = i;
if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
if (maxPos == i) break;
swap(a, i, maxPos);
i = maxPos;
}
}
时间复杂度: O(n)
对下标从n/2 开始到 1 的数据进行堆化,下标是 n/2 + 1 到 n的节点是叶子节点,不需要堆化,实际上,对于完全二叉树,下标从 n/2 + 1 到 n的节点都是叶子节点。
4、堆的基本操作
1> 往堆中插入一个元素(注意插入一个元素后要继续满足堆的两个特性)
- 先将元素放入到底层空间中(注意:空间不够时需要扩容)
- 将最后新插入的节点向上调整,直到满足堆的性质
private static void buildHeap(int[] a, int n) {
for (int i = n/2; i >= 1; --i) {
heapify(a, n, i);
}
}
private static void heapify(int[] a, int n, int i) {
while (true) {
int maxPos = i;
if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
if (maxPos == i) break;
swap(a, i, maxPos);
i = maxPos;
}
}
2> 删除堆顶元素
根据堆的定义我们知道堆顶存储的就是最大值或者最小值
- 将堆顶元素与堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素向下调整
public void removeMax() {
if (count == 0) return -1; // 堆中没有数据
a[1] = a[count];
--count;
heapify(a, count, 1);
}
private void heapify(int[] a, int n, int i) { // 自上往下堆化
while (true) {
int maxPos = i;
if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
if (maxPos == i) break;
swap(a, i, maxPos);
i = maxPos;
}
}
注意:容易产生的误区:
假设我们构造的是大顶堆,堆顶元素就是最大的元素。当我们删除堆顶元素之后,就需要把第二大的元素放到堆顶,那第二大元素肯定会出现在左右子节点中。然后我们再迭代地删除第二大节点,以此类推,直到叶子节点被删除。这样最后堆化的堆不满足二叉树特性