8.1、定义
-
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵 完全二叉树 的 数组对象
-
堆的特性(以大顶堆为例)
-
是特殊的 完全二叉树
除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的, 那么要求 左满右不满
特殊之处:
结点大于等于它的两个子结点;右子结点也不比它大
-
通常用 数组 来实现
具体方法就是将二叉树的结点按照 层级顺序 放入数组中, 根结点在 位置1(数组索引0处不存储数据),它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推
-
如果一个结点的位置为 k,则它的父结点的位置为 k/2
-
两个子结点的位置则分别为 2k 和 2k+1
这样,在不使用指针的情况下,也可以通过计算数组的索引在树中上下移动:
从 a[k] 向上一层,就令k等于 k/2
向下一层就令k等于 2k 或 2k+1
-
-
每个结点都 大于等于 它的两个子结点
这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的;可以左子结点大于右子结点
区别于普通的二叉树:
一般二叉树中右子结点大于父结点
-
8.2、API
8.3、实现
1)Insert
堆是用 数组 完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以要往堆中插入数据,只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要 大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候就需要通过一些方法,让刚才插入的这个数据放入到合适的位置
所以,如果往堆中新插入元素,只需要不断的比较新结点 a[k] 和它的父结点 a[k/2] 的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。
2)delMax
由堆的特性可以知道,索引1处的元素,也就是根结点就 是最大的元素,把根结点的元素删除后,需要有一个新的根结点出现,这时可以 暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候就需要通过一些方法,让这个新的根结点放入到合适的位置
所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点 a[k] 与它的子结点a[2k] 和 a[2k+1] 中的较大者交换位置,即可完成堆的有序调整。
3)代码
package chapter06;
/**
* @author 土味儿
* Date 2021/9/8
* @version 1.0
* 堆
*/
public class Heap<T extends Comparable<T>> {
/**
* 存储元素的数组
*/
private T[] items;
/**
* 元素个数
*/
private int n;
/**
* 构造器
*
* @param capacity
*/
public Heap(int capacity) {
//this.items = (T[]) new Object[capacity+1];
this.items = (T[]) new Comparable[capacity+1];
this.n = 0;
}
/**
* 插入元素t
*
* @param t
*/
public void insert(T t) {
// 在结尾插入
items[++n] = t;
// 让新元素上浮,找到合适的位置
swim(n);
}
/**
* 删除最大元素并返回
*
* @return
*/
public T delMax() {
// 最大元素
T max = items[1];
// 交换索引1处 和 最大索引处的元素,让完全二叉树中最右的元素成为临时根结点
exch(1, n);
// 删除交换后最大索引处的元素,即原根结点(最大元素)
items[n] = null;
// 元素数量减1
n--;
// 让新根结点下沉,找到合适位置
sink(1);
return max;
}
/**
* 判断索引i处的元素是否比j处的小
*
* @param i
* @param j
* @return
*/
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
/**
* 交换索引i处与j处的元素
*
* @param i
* @param j
*/