基于数组实现最大堆
实现功能:
1.添加元素(元素上浮)
2.取出元素(元素下沉)
3.当已知当前节点下标时,返回当前节点的左右孩子及父节点下标
4.替换堆顶元素
5.将任意数组转换为堆结构
实现代码:
HeapTest.java
package heap;
import java.util.Arrays;
import java.util.Comparator;
//基于数组的堆
public class HeapTest<E>{
//堆结构为完全二叉树,值要求为任意一个节点要大于自身的左右节点的值(最大堆)
//故需要提供比较器进行数值比较
private Comparator<E> comparator;
private int size;
private E[] elemetData;
//设置数组初始化的的默认值
private static final int DEFAULT_CAPTITY=10;
//通过构造方法初始化数组及传入外部比较器
public HeapTest(){
this(DEFAULT_CAPTITY,null);
}
public HeapTest(int initCaptity){
this(initCaptity,null);
}
public HeapTest(int initCaptity,Comparator<E> comparator){
elemetData= (E[]) new Object[initCaptity];
this.comparator=comparator;
}
//将任意一个数组变为最大堆的形式
public HeapTest(E[] arr){
elemetData= (E[]) new Object[arr.length];
for(int i=0;i<elemetData.length;i++)
elemetData[i]=arr[i];
//更新size
size=elemetData.length;
//此处是将任意一个数组初步建立为堆结构后,从最后一个非叶子节点处向根节点一次做下沉操作,
//原因是所有叶子节点单独来看均是一个堆,故叶子结点都是满足堆结构的,只有非叶子节点可能不满足,故此种方式可大大提高效率
for(int i=(arr.length-1-1)/2;i>=0;i--)
siftDown(i);
}
//获得当前堆元素个数
public int getSize(){
return size;
}
//判断当前堆是否为空
public boolean isEmpty(){
return size==0;
}
//比较方法
private int compare(E o1,E o2){
if(comparator==null)
return ((Comparable<E>)o1).compareTo(o2);
return comparator.compare(o1,o2);
}
//获得当前节点的左孩子下标
public int leftChildIndex(int index){
return 2*index+1;
}
//获得当前节点的右孩子下标
public int rightChildIndex(int index){
return 2*index+2;
}
//获得当前节点的父节点下标
public int parentIndex(int index){
if(index==0)
throw new IllegalArgumentException("你没有爸爸");
return (index-1)/2;
}
//向堆中添加元素,因堆由数组实现,故添加元素时从数组最后插入方便
//但此时就不能确保该结构还是堆,故需将插入元素上浮到合适位置使得当前结构依旧满足堆结构
public void add(E e){
//当当前数组存储已满时,对其进行扩容
if(size==elemetData.length)
grow();
//否则将插入元素添加至数组尾部
elemetData[size++]=e;
//将插入元素上浮以满足堆结构
//size++走到了下一位,需传入数组最后一个元素的下标
siftUp(size-1);
}
//因数组可能出现存储满的情况,故需要扩容
private void grow(){
int oldCap=elemetData.length;
int newCap=oldCap+(oldCap<64?oldCap:oldCap>>1);
if(newCap>Integer.MAX_VALUE-8)
throw new IndexOutOfBoundsException("数组太大");
elemetData= Arrays.copyOf(elemetData,newCap);
}
//元素上浮
private void siftUp(int index){
//当插入元素下标大于0(即不为根节点)且插入元素值大于其父节点值时,需要将其不断上浮,直到到根节点位置或小于其父节点的值才停止上浮
while (index>0 && compare(elemetData[index],elemetData[parentIndex(index)])>0){
//将插入元素与其父节点值进行交换(即为插入元素一次上浮操作)
swap(index,parentIndex(index));
//将插入元素下标更新为父节点下标,为下次上浮做准备
index=parentIndex(index);
}
}
private void swap(int indexA,int indexB){
E temp=elemetData[indexA];
elemetData[indexA]=elemetData[indexB];
elemetData[indexB]=temp;
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
for(E e:elemetData){
if(e!=null)
res.append(e+" ");
}
return res.toString();
}
//取得堆中最大元素
public E getMax(){
if(isEmpty())
throw new IllegalArgumentException("heap is empty");
return elemetData[0];
}
//取得堆中最大元素并删除
//最大堆最大值一定是根节点,当删除根节点时需要改变堆结构,使得当前最大值称为新的根节点
//通过对比得知,将堆中最大值删除后可将数组最后一个元素放于根节点位置,而后再将根节点与左右孩子值比较进行下沉操作
//下沉思路:将根节点与左右孩子值对比,与较大值交换,直到叶子结点或当前节点比左右孩子值都大时停止下沉操作
//将堆中最大值删除后返回删除的最大值
public E extractMax(){
E result=getMax();
swap(0,size-1);
elemetData[--size]=null;
siftDown(0);
return result;
}
//将二叉树下沉到合适位置
private void siftDown(int index){
//在堆中非叶子节点一定有左孩子但不一定有右孩子
//将当前节点的左孩子下标暂存于临时变量j
while (leftChildIndex(index)<size){
int j=leftChildIndex(index);
//若j+1<size则说明该节点有左右孩子
if(j+1<size){
//找出左右孩子的较大值
if(compare(elemetData[j],elemetData[j+1])<0)
j++;
}
//此时j为左右孩子中较大值的下标
if(compare(elemetData[index],elemetData[j])>0)
break;
//将当前节点与左右孩子的较大值交换
swap(index,j);
index=j;
}
}
//替换堆顶元素
public E prepare(E newValue){
E result=getMax();
elemetData[0]=newValue;
siftDown(0);
return result;
}
}
Test.java
Public Test{
public static void main(String[] args) {
HeapTest<Integer> heap=new HeapTest<>();
int[] data=new int[]{3,2,9,0,22,34,10,5,8,9};
for(int i:data)
heap.add(i);
heap.add(18);
System.out.println("向堆中添加元素:");
System.out.println(heap);
System.out.println("--------------------------------");
HeapTest<Integer> heap1=new HeapTest<>();
int[] num=new int[]{1,2,3,4,5,6,7,8,9,10};
for(int i:num)
heap1.add(i);
List<Integer> list=new ArrayList<>();
while (!heap1.isEmpty()){
list.add(heap1.extractMax());
}
System.out.println("不断取出堆中最大元素:");
System.out.println(list);
System.out.println("--------------------------------");
Integer[] nums=new Integer[]{9,8,7,6,5,4,3,2,1,10};
HeapTest<Integer> heapTest=new HeapTest<>(nums);
System.out.println("将任意数组变为堆结构:");
System.out.println(heapTest);
}
}
输出结果:
向堆中添加元素:
34 18 22 8 9 3 10 0 5 2 9
--------------------------------
不断取出堆中最大元素:
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
--------------------------------
将任意数组变为堆结构:
10 9 7 6 8 4 3 2 1 5