堆 - 手动改写堆

之前的文章中有提到,Java中的PriorityQueue默认就是个小根堆的堆结构,如果想切换大根堆,则自己实现比较器即可,那么既然已经有了系统提供的堆结构和对应的API为什么还要自己手写呢?

因为系统提供的PriorityQueue包括Java系统包中所提供的功能无法满足我们一些特有的需求!!!

比如说:

  1. 我在PriorityQueue中用Student类的score属性做了一个小顶堆的排序,score小的会排在上面,那如果此时我修改某个学生的score,对于PriorityQueue来讲,它不是动态调整的,你修改了某个元素之后,PriorityQueue不能跟着一起调整,那这个PriorityQueue就没有用了。
  2. 堆的本质其实就是一个数组的结构,包括PriorityQueue的底层也是只有一个Object[]来存储数据,那如果PriorityQueue中有a,b,c三个元素,对应着数组下标0,1,2三个位置,此时通过数组下标可以知道下标位置存储着对应的数据,那如何能知道a,b,c三个元素存储在哪呢?

数组中是没有反向索引的,如果想知道c的位置,那只有通过遍历整个数组才可以找到。
包括想要删除任意的一个元素,PriorityQueue包括Java类是没有提供对应的方法的,所以就体现了手动改写堆的重要性!!!

代码实现

import java.util.*;

/*
* T一定要是非基础类型,有基础类型需求包一层
* */
public class HeapGreater<T> {

    //用来实际存储堆结构的List
    private List<T> heap;
    //反向索引,map的value会存储元素对应的索引位置
    private Map<T, Integer> indexMap;
    //堆大小,如果是用array存储堆结构的话,那可能会很大
    private Integer heapSize;
    //根据自己的业务实现比较器
    private Comparator<? super T> com;

    public HeapGreater(Comparator<? super T> com) {
        this.com = com;
        heap = new ArrayList<>();
        indexMap = new HashMap<>();
        heapSize = 0;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }

    public int getSize() {
        return heapSize;
    }

    public T peek() {
        return heap.get(0);
    }

    public boolean contains(T obj) {
        return indexMap.containsKey(obj);
    }

    //堆中添加元素
    public void add(T obj) {
        //List中添加
        heap.add(obj);
        //反向索引中添加元素 ,heapSize为元素索引位置
        indexMap.put(obj, heapSize);
        //排序,并且heapSize++
        heapInsert(heapSize++);
    }

    //如果元素有修改,则直接丢进来重新构建一下堆结构
    public void resign(T obj) {
        //下面两个方法只会执行其中一个
        heapInsert(indexMap.get(obj));
        heapify(indexMap.get(obj));
    }

    //弹出栈顶元素
    public T pop() {
        //获取list中第一个元素并返回
        T ans = heap.get(0);
        //将最后一个元素和第一个元素交换位置
        swap(0, heapSize - 1);
        //移除最后一个元素(0和之前的最后一个元素已经交换了位置)
        heap.remove(--heapSize);
        indexMap.remove(ans);
        //将新放上去的元素排序
        heapify(0);
        return ans;
    }

    //移除元素
    public void remove(T obj) {
        //先找到最后一个元素作为remove元素的替代
        T replace = heap.get(heapSize - 1);
        //获取要remove元素的反向索引
        int index = indexMap.get(obj);
        //堆中remove最后一个元素
        heap.remove(--heapSize);
        //反向索引中删除要remove的元素
        indexMap.remove(obj);
        //如果要remove的元素不是最后一个元素
        if (obj != replace) {
            //replace替换到要remove元素的位置
            heap.set(index, replace);
            //反向索引替换到要remove元素的位置
            indexMap.put(replace, index);
            //将replace进行排序
            resign(replace);
        }

    }

    //上升操作,具体的比较大小需看Comparator的实现
    private void heapInsert(int index) {
        while (com.compare(heap.get(index), heap.get((index - 1 / 2))) < 0) {
            swap(index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //下沉操作,同heapInsert
    private void heapify(int index) {
        int left = index * 2 + 1;
        while (left < heapSize) {
            int best = left + 1 < heapSize && com.compare(heap.get(left + 1), heap.get(left)) < 0 ? left + 1 : left;
            best = com.compare(heap.get(best), heap.get(index)) < 0 ? best : index;
            if (best == index) {
                break;
            }
            swap(best, index);
            index = best;
            left = index * 2 + 1;
        }
    }

    //交换两个数,同时交换元素位置和反向索引的位置。
    private void swap(int i, int j) {
        T obj1 = heap.get(i);
        T obj2 = heap.get(j);

        heap.set(j, obj1);
        heap.set(i, obj2);

        indexMap.put(obj1, j);
        indexMap.put(obj2, i);

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值