基于数组实现最大堆

本文介绍了如何使用数组来实现最大堆,包括添加元素、取出元素、查找节点关系、替换堆顶元素以及将数组转换为堆结构等功能,并给出了相应的实现代码。
摘要由CSDN通过智能技术生成

基于数组实现最大堆

实现功能:

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 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值