加强堆结构说明
加强堆结构说明
作者:Grey
原文地址:
关于堆和堆排序的说明
可以参考这篇博客:与堆和堆排序相关的问题
基础的堆结构可以实现数据入堆和出堆以后(即: 调用堆的 pop 和 push 方法),使用\(O(logN)\)的时间复杂度可以将堆调整好,如果使用的是 Java 语言,可以用 java.util
包中的 PriorityQueue
实现堆的所有操作。
但是,在实际场景中,有一种情况是:在已知的一个堆中,堆中任意一个元素变换后,也要以\(O(logN)\)的时间复杂度把堆结构调整正确。这是 Java 语言自带的堆结构(PriorityQueue
)无法做到的,这就引入了「加强堆」的概念。「加强堆」提供如下方法
public void resign(T obj) {}
这个方法表示,对于堆中任意的一个元素 obj,如果调整了其对应的数值,整个堆结构还能在时间复杂度\(O(logN)\)下调整好。
普通堆结构之所以无法做到,是因为普通的堆结构没有记录任意一个数据所在的位置信息,所以无法从对应的位置进行堆结构调整。所以,「加强堆」结构引入了一个 HashMap
:
HashMap<T, Integer> indexMap; // 元素在堆中的位置
有了这个HashMap
, 就可以很方便得到某个数据项在堆中的位置是什么,在堆的pop
和push
方法中,要把HashMap
的逻辑加入
public void push(T obj) {
heap.add(obj);
indexMap.put(obj, heapSize);
heapInsert(heapSize++);
}
public T pop() {
T ans = heap.get(0);
swap(0, heapSize - 1);
indexMap.remove(ans);
heap.remove(--heapSize);
heapify(0);
return ans;
}
更重要的是,在堆的 heapify
和 heapInsert
操作中,涉及到的堆中两个元素的交换,也要把堆中元素的位置进行交换。
// 不要忘记把堆中元素的位置也要做交换!!!!
private void swap(int i, int j) {
T o1 = heap.get(i);
T o2 = heap.get(j);
heap.set(i, o2);
heap.set(j, o1);
indexMap.put(o2, i);
indexMap.put(o1, j);
}
以上是铺垫,到了最核心的resign
方法,其逻辑如下
public void resign(T obj) {
heapInsert(indexMap.get(obj));
heapify(indexMap.get(obj));
}
整个过程也非常好理解,就是找到变化的那个数据项的位置,然后执行heapify
和heapInsert
,由于变换过程无非是变大或者变小,所以找到变换的位置,heapify
和heapInsert
操作只会执行一个操作!另外一个操作进去以后会直接跳出。
加强堆的完整代码如下(支持泛型):
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
/**
* @param <T>
* @since 21
*/
// 加强堆
// 笔记:https://www.cnblogs.com/greyzeng/p/16936506.html
public class HeapGreater<T> {
private final ArrayList<T> heap;
private final HashMap<T, Integer> indexMap; // 元素在堆中的位置
private int heapSize; // 和heap配合使用
private final Comparator<? super T> comp;
public HeapGreater(Comparator<T> c) {
heap = new ArrayList<>();
indexMap = new HashMap<>();
comp = c;
}
public boolean isEmpty() {
return heapSize == 0;
}
public int size() {
return heapSize;
}
public boolean contains(T obj) {
return indexMap.containsKey(obj);
}
public T peek() {
// since jdk 21
// before jdk 21 use
// heap.get(0)
return heap.getFirst();
}
public void push(T obj) {
heap.add(obj);
indexMap.put(obj, heapSize);
heapInsert(heapSize++);
}
public T pop() {
// since jdk 21
// before jdk 21 use
// heap.get(0)
T ans = heap.getFirst();
swap(0, heapSize - 1);
indexMap.remove(ans);
heap.remove(--heapSize);
heapify(0);
return ans;
}
public void remove(T obj) {
T replace = heap.get(heapSize - 1);
int index = indexMap.get(obj);
indexMap.remove(obj);
heap.remove(--heapSize);
if (obj != replace) { // obj == replace表示删掉的是最后一个位置的数据,此时不需要进行resign操作
heap.set(index, replace);
indexMap.put(replace, index);
resign(replace);
}
}
public void resign(T obj) {
heapInsert(indexMap.get(obj));
heapify(indexMap.get(obj));
}
// 请返回堆上的所有元素
public List<T> getAllElements() {
return new ArrayList<>(heap);
}
private void heapInsert(int index) {
while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void heapify(int index) {
int left = index * 2 + 1;
while (left < heapSize) {
int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left;
best = comp.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 o1 = heap.get(i);
T o2 = heap.get(j);
heap.set(i, o2);
heap.set(j, o1);
indexMap.put(o2, i);
indexMap.put(o1, j);
}
}
使用对数器对上述加强堆进行测试,测试代码如下(这里利用了Java自带的PriorityQueue
来测试)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Objects;
import java.util.PriorityQueue;
@DisplayName("加强堆结构测试")
public class HeapGreaterTest {
// 加强堆测试
@Test
@DisplayName("加强堆结构测试")
public void testGreaterHeap() {
int value = 1000;
int limit = 100;
int testTimes = 2000000;
for (int i = 0; i < testTimes; i++) {
int curLimit = (int) (Math.random() * limit) + 1;
HeapGreater<Integer> myHeap = new HeapGreater<>((o1, o2) -> o2 - o1);
PriorityQueue<Integer> rightHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
int curOpTimes = (int) (Math.random() * limit);
for (int j = 0; j < curOpTimes; j++) {
if (rightHeap.isEmpty()) {
int curValue = (int) (Math.random() * value);
myHeap.push(curValue);
rightHeap.add(curValue);
} else if (rightHeap.size() == curLimit) {
if (!Objects.equals(myHeap.pop(), rightHeap.poll())) {
Assertions.fail();
}
} else {
if (Math.random() < 0.5) {
int curValue = (int) (Math.random() * value);
myHeap.push(curValue);
rightHeap.add(curValue);
} else {
if (!Objects.equals(myHeap.pop(), rightHeap.poll())) {
Assertions.fail();
}
}
}
}
}
}
}
注:运行上述代码需要引入 junit
相关的包
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
使用加强堆来解决的问题示例,见使用加强堆解决 topK 问题
更多
本文来自博客园,作者:Grey Zeng,转载请注明原文链接:https://www.cnblogs.com/greyzeng/p/16936506.html