4-2 堆的基本实现
如果一个算法的时间复杂度是 O(nlogn) ,那么算法执行的形状一定看起来像是一棵树一样(想一想我们的归并排序),所以堆在形式上就像一棵树。
二叉堆 Binary Heap 的特点
- 堆中某个节点的值总是不大于其父节点的值
最大堆:顶上的元素是堆中所有元素的最大值。 - 堆总是一棵完全二叉树
从形状上看,除了最后一层之外,其它层节点的数量应该是最大值,并且最后一层的节点应该集中在左侧。
使用数组实现二叉堆
为什么使用数组
因为最大堆是一棵完全二叉树,我们当然可以使用树这个数据结构来实现。但是,既然堆是一棵完全二叉树,回想一下完全二叉树的定义,我们使用数组来实现二叉堆(因为完全二叉树的性质,二叉树的索引可以很轻松地与数组的索引建立一一对应的关系),而事实上,数组也是堆的经典实现。
使用数组实现二叉堆的方式是:自上到下、从左到右对索引进行标记,第 0 号索引不使用,从 1 开始编号(根节点从1 开始标记,这是一种经典的做法)。
之所以,第 0 号索引不使用,是因为从 1 开始索引,对于二叉堆来说,有比较如下两条简单的性质,我们无畏浪费一个元素的空间。如果我们不明白为什么这么做,可以自己尝试从第 0 个位置开始索引(以后我们也会遇到这种情况),只不过是增大了一些计算量而已。
从索引为 1 的位置开始放置元素的数组实现的二叉堆的性质
我们自己画一个二叉堆(如下图),从索引为 1 的位置开始放置元素,把索引标注在二叉堆上,如下图,
观察下标的位置,对应数组的索引,以下基本性质就可以一目了然。
性质1:左节点的编号是父节点(如果父亲节点存在)的编号的 2 倍;
性质2:右节点的编号是父节点(如果父亲节点存在)的编号的 2 倍加 1。
我们可以使用数学归纳法得到如上的性质。
所以我们要想找到一个节点的父亲节点(如果父亲节点存在)可以使用公式:&parent(i)=i/2$,(这个除法是计算机的除法,除不开的话向下取整)。
要想找到两个子节点(如果孩子节点存在)可以使用公式: leftchild=2i,rightchild=2i+1 。
下面,我们以最大堆为例,讨论最大堆须要实现的 API。
最大堆(MaxHeap)的实现的基本框架
下面的代码展示了一个最大堆的基本实现框架:
public class MaxHeap {
private int[] data;
private int count; // 堆中真实元素的个数
public MaxHeap(int capacity) {
// 开辟数组空间(整个数组存储从索引 1 开始)
data = new int[capacity + 1];
count = 0;
}
public int size() {
return count;
}
// 当前堆中的元素个数是否为 0
public boolean isEmpty() {
return count == 0;
}
}