之前的文章中有提到,Java中的PriorityQueue默认就是个小根堆的堆结构,如果想切换大根堆,则自己实现比较器即可,那么既然已经有了系统提供的堆结构和对应的API为什么还要自己手写呢?
因为系统提供的PriorityQueue包括Java系统包中所提供的功能无法满足我们一些特有的需求!!!
比如说:
- 我在PriorityQueue中用Student类的score属性做了一个小顶堆的排序,score小的会排在上面,那如果此时我修改某个学生的score,对于PriorityQueue来讲,它不是动态调整的,你修改了某个元素之后,PriorityQueue不能跟着一起调整,那这个PriorityQueue就没有用了。
- 堆的本质其实就是一个数组的结构,包括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);
}
}