堆–完全二叉树的数组实现
堆的定义
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以看作是一棵完全二叉树的数组对象。
堆的特性
1.它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
2.它通常用数组来实现。
具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。
A,E,G,H,I,N,O,P,R,S,T
如果一个结点的位置为k,则它的父结点的位置为[k/2],而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。
3.每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟二叉查找树是有区别的。
堆的API设计
类名 | Heap |
---|---|
构造方法 | Heap(int capacity):创建容量为capacity的Heap对象 |
成员方法 | 1.private boolean less(int i,int j):判断堆中索引i处的元素是否小于索引j处的元素 2.private void exch(int i,int j):交换堆中i索引和j索引处的值 3.public T delMax():删除堆中最大的元素,并返回这个最大元素 4.public void insert(T t):往堆中插入一个元素 5.private void swim(int k):使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置 6.private void sink(int k):使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置 |
成员变量 | 1.private T[] imtes : 用来存储元素的数组 2.private int N:记录堆中元素的个数 |
堆的实现
insert插入方法的实现
堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。
所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整
delMax删除最大元素方法的实现
由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。
所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点a[k]与它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。
堆的代码实现
/**
* 堆--完全二叉树的数组实现
* A,E,G,H,I,N,O,P,R,S,T(大->小)
*/
public class Heap<T extends Comparable<T>> {
// 存储堆中的元素
private T[] items;
// 记录堆中元素的个数
private int N;
public Heap(int capacity) {
items = (T[]) new Comparable[capacity + 1];
N = 0;
}
// 往堆中插入一个元素
public void insert(T t) {
items[++N] = t;
swim(N);
}
// 删除堆中最大的元素,并返回这个最大元素
public T delMax() {
// 记录最大的元素
T max = items[1];
// 删除最大的元素(让完全二叉树中的最右侧的元素变为临时根结点)
exch(1, N);
// 最大元素的位置变更为N索引的位置,删除N索引位置的元素
items[N] = null;
// 元素个数-1
N--;
// 保持队中的规则,调用下沉算法
sink(1);
return max;
}
// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
// 通过循环,不断地比较当前结点的值与父结点的值,如果当前结点的值>其父结点的值,交换两处的值
while (k > 1) {
if (less(k / 2, k)) {
exch(k / 2, k);
}
k /= 2;
}
}
// 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
// 通过循环,不断的比较当前结点的值与其两个子结点中最大的结点的值,如果当前结点的值>其最大子结点的值,交换两处的值
while (2 * k <= N) {
// 获取当前结点的两个子结点中最大的值,记录索引
int max;
if ((2 * k + 1) <= N) { // 存在右子结点
if (less(2 * k + 1, 2 * k)) {
max = 2 * k;
} else {
max = 2 * k + 1;
}
} else { // 不存在右子结点
max = 2 * k;
}
// 比较当前结点和最大子结点,如果大于结束循环
if (!less(k, max)) {
break;
}
// 如果小于,交换两位置的值
exch(k, max);
k = max;
}
}
// 判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
// 交换堆中i索引和j索引处的值
private void exch(int i, int j) {
T tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
}