通过学习自定义堆,了解堆的数据结构。
本篇以最大堆为例。
底层依赖了自定义数组, 参考:自定义数组 中的 Array.java
所以,其时间复杂度分析:
add | O(log n) |
extractMax | O(log n) |
replace | O(log n) |
对 siftUP方法的解释:
每次将要添加的元素,放在数组最后一个位置,按照最大堆的定义,使其值与父节点的值进行比较,决定是否“上浮”。
对siftDown方法的解释:
将位于数组最后位置的元素与最大元素交换位置,并删除最大元素,对此时处于最大元素位置的元素,安装最大堆的定义,比较其值与左右孩子中最大的那个的大小,进行“下沉”。
对replace方法的解释:直接将堆顶元素替换以后sift Down。
Array.java 新增了一个方法:
/**
* 新增
* 用于交换两个位置的值
* @param i
* @param j
*/
public void swap(int i,int j) {
if(i < 0 || i >= size || j < 0 || j >= size) {
throw new IllegalArgumentException("索引不合法");
}
E e=data[i];
data[i]=data[j];
data[j]=e;
}
MaxHeap.java:
package Heap;
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;//维护一个自定义数组
/**
* 带参构造方法
* @param capacity
*/
public MaxHeap(int capacity) {
data =new Array<>(capacity);
}
/**
* 无参构造方法
*/
public MaxHeap() {
data=new Array<>();
}
/**
* 返回堆中的元素个数
* @return
*/
public int size() {
return data.getSize();
}
/**
* 判断堆是否为空
* @return
*/
public boolean isEmpty() {
return data.isEmpty();
}
/**
* 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
* @param index
* @return
*/
private int parent(int index) {
if(index==0) {
throw new IllegalArgumentException("索引为0,没有父节点");
}
return (index-1)/2;
}
/**
* 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
* @param index
* @return
*/
private int leftChild(int index) {
return index*2+1;
}
/**
* 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
* @param index
* @return
*/
private int rigthChild(int index) {
return index*2+2;
}
/**
* 向堆中添加元素。
* @param e
*/
public void add(E e) {
data.addLast(e);
siftUp(data.getSize()-1);
}
private void siftUp(int index) {
while(index>0 && data.get(parent(index)).compareTo(data.get(index))<0) {
data.swap(index, parent(index));
index=parent(index);
}
}
/**
* 查看堆中的最大元素
* @return
*/
public E findMax() {
if(data.getSize() == 0) {
throw new IllegalArgumentException("堆为空");
}
return data.get(0);
}
/**
* 取出堆中最大元素
* @return
*/
public E extractMax() {
E e=findMax();
data.swap(0, data.getSize()-1);
data.removeLast();
siftDown(0);
return e;
}
private void siftDown(int i) {
while(leftChild(i)<data.getSize()) {
int k=leftChild(i);
if(k+1<data.getSize()&& data.get(k+1).compareTo(data.get(k))>0) {
k++;
}
//此时,data[k]是leftChild和rightChild中最大值
if(data.get(i).compareTo(data.get(k))>=0) {
break;
}
data.swap(k, i);
i=k;
}
}
/**
* 取出堆中最大的元素,并且替换成元素e
* @param e
* @return
*/
public E replace(E e) {
E ret=findMax();
data.set(0, e);
siftDown(0);
return ret;
}
}