设计背景
二叉树有满二叉树(Full binary Tree)和完全二叉树(Complete Binary Tree)等特殊情况,满二叉树指“除了叶子节点外,所有节点都有左子树和右子树”,它的叶子节点只会出现在最后一层;而完全二叉树因为有的节点可能缺失子树,所以它的叶子节点可能出现在最后的两层中。
而堆(Heap)可以看作一颗完全二叉树的数组,堆有最大堆(MaxHeap)和最小堆(MinHeap)之分,最大堆中某个节点的值一定不大于其父节点的值,而最小堆中某个节点的值一定不小于其父节点的值。
结构分析
【底层实现】动态数组(ArrayList)
【核心方法】
public void add(E e); //添加元素
private void siftUp(int k); //元素上浮
public E extractMax(); //删除堆中最大的元素
private void siftDown(int k); // 元素下浮
public E replace(E e); //替换堆中指定的元素
public E findMax(); // 找到堆中最大的元素
【特别注意】堆所依赖的数组需要增加两个方法:
/**
* 带参构造器:对实例域进行初始化
* @param arr 数组
*/
public ArrayList(E[] arr) {
data = (E[])new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
/**
* 方法:交换两个元素的位置
* @param i 第一个元素的索引
* @param j 第二个元素的索引
*/
public void swap(int i, int j) {
if (i < 0 || i >= size || j < 0 || j >= size) {
throw new IllegalArgumentException("Index is illegal!");
}
E t = data[i];
data[i] = data[j];
data[j] = t;
}
代码实现
利用动态数组实现堆:
public class MaxHeap<E extends Comparable<E>> {
/**
* 实例域:动态数组
*/
private ArrayList<E> data;
/**
* 带参构造器:对实例域进行初始化
* @param capacity 堆的容量
*/
public MaxHeap(int capacity) {
data = new ArrayList<>(capacity);
}
/**
* 带参构造器:将数组重新排序为MaxHeap的形式,并初始化实例域
* @param arr
*/
public MaxHeap(E[] arr) {
data = new ArrayList<>(arr);
for (int i = parent(arr.length - 1); i >= 0; i--) {
siftDown(i);
}
}
/**
* 无参构造器:对实例域进行初始化
*/
public MaxHeap() {
data = new ArrayList<>();
}
/**
* 方法:向堆中添加元素
* @param e 元素
*/
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
/**
* 方法:元素上浮
* @param k 指定元素的索引
*/
private void siftUp(int k) {
// 如果k不为0,且k对应的元素比其父元素大,则执行循环
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
// 将k对应的元素与其父元素交换位置
data.swap(k, parent(k));
// 更新k的信息
k = parent(k);
}
}
/**
* 方法:查找堆中最大的元素
* @return 最大的元素
*/
public E findMax() {
if (data.getSize() == 0) {
throw new IllegalArgumentException("The heap is empty!");
}
return data.get(0);
}
/**
* 方法:取出堆中最大元素
* @return 最大元素
*/
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
/**
* 方法:元素下浮
* @param k 元素的索引
*/
private void siftDown(int k) {
// 若k有左子树,则执行循环
while (leftChild(k) < data.getSize()) {
// 获取k左子树的索引
int j = leftChild(k);
// 如果k有右子树,而且k的右子树比左子树大
if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
// 获取k右子树的索引
j = rightChild(k);
}
// 如果k与子树间的关系正常,则结束循环
if (data.get(k).compareTo(data.get(j)) >= 0) {
break;
}
// 如果k与子树间的关系异常,则发生交换(下浮)
data.swap(k, j);
// 更新k的索引
k = j;
}
}
/**
* 方法:将堆中最大元素替换为e
* @param e 新元素
* @return 被替换的元素
*/
public E replace(E e) {
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
/**
* 方法:找到指定索引元素的父节点的索引
* @param index 指定索引
* @return 父节点的索引
*/
private int parent(int index) {
if (index == 0) {
throw new IllegalArgumentException("The root doesn't have parent!");
}
return (index - 1) / 2;
}
/**
* 方法:找到指定索引元素的左子树的索引
* @param index 指定索引
* @return 左子树的索引
*/
private int leftChild(int index) {
return index * 2 + 1;
}
/**
* 方法:找到指定索引元素的右子树的索引
* @param index 指定索引
* @return 右子树的索引
*/
private int rightChild(int index) {
return index * 2 + 2;
}
/**
* 方法:获取堆的元素个数
* @return
*/
public int size() {
return data.getSize();
}
/**
* 方法:判断堆是否为空
* @return
*/
public boolean isEmpty() {
return data.isEmpty();
}
}