• 加强堆结构说明


    加强堆结构说明

    作者:Grey

    原文地址:

    博客园:加强堆结构说明

    CSDN:加强堆结构说明

    关于堆和堆排序的说明

    可以参考这篇博客:与堆和堆排序相关的问题

    基础的堆结构可以实现数据入堆和出堆以后(即: 调用堆的 pop 和 push 方法),使用 O ( l o g N ) O(logN) O(logN)的时间复杂度可以将堆调整好,如果使用的是 Java 语言,可以用 java.util 包中的 PriorityQueue 实现堆的所有操作。

    但是,在实际场景中,有一种情况是:在已知的一个堆中,堆中任意一个元素变换后,也要以 O ( l o g N ) O(logN) O(logN)的时间复杂度把堆结构调整正确。这是 Java 语言自带的堆结构(PriorityQueue)无法做到的,这就引入了「加强堆」的概念。「加强堆」提供如下方法

    public void resign(T obj) {}
    
    • 1

    这个方法表示,对于堆中任意的一个元素 obj,如果调整了其对应的数值,整个堆结构还能在时间复杂度 O ( l o g N ) O(logN) O(logN)下调整好。

    普通堆结构之所以无法做到,是因为普通的堆结构没有记录任意一个数据所在的位置信息,所以无法从对应的位置进行堆结构调整。所以,「加强堆」结构引入了一个 HashMap:

    HashMap<T, Integer> indexMap; // 元素在堆中的位置
    
    • 1

    有了这个HashMap, 就可以很方便得到某个数据项在堆中的位置是什么,在堆的poppush方法中,要把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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    更重要的是,在堆的 heapifyheapInsert 操作中,涉及到的堆中两个元素的交换,也要把堆中元素的位置进行交换

    // 不要忘记把堆中元素的位置也要做交换!!!!
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以上是铺垫,到了最核心的resign方法,其逻辑如下

    public void resign(T obj) {
        heapInsert(indexMap.get(obj));
        heapify(indexMap.get(obj));
    }
    
    • 1
    • 2
    • 3
    • 4

    整个过程也非常好理解,就是找到变化的那个数据项的位置,然后执行heapifyheapInsert,由于变换过程无非是变大或者变小,所以找到变换的位置,heapifyheapInsert操作只会执行一个操作!另外一个操作进去以后会直接跳出。

    加强堆的完整代码如下(支持泛型):

    
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    
    /**
     * @param 
     * @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    使用对数器对上述加强堆进行测试,测试代码如下(这里利用了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();
                            }
                        }
                    }
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    注:运行上述代码需要引入 junit 相关的包

    <dependency>
        <groupId>org.junit.jupitergroupId>
        <artifactId>junit-jupiterartifactId>
        <version>5.10.1version>
        <scope>testscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用加强堆来解决的问题示例,见使用加强堆解决 topK 问题

    更多

    算法和数据结构学习笔记

    算法和数据结构学习代码

  • 相关阅读:
    前端个人实训笔记
    Cube.js 试试这个新的数据分析开源工具
    Linux shell 逻辑运算符、逻辑表达式详解
    15、用户web层服务(三)
    五万字142道超全前端面试题---送给在校招的你
    React Hook - 自定义Hook的基本使用和案例练习
    Qt项目实战 杂谈一二:中文乱码事情小,处理不好头发少
    kubernetes(K8S)集群yaml常见用法
    一些常用的刷题网站
    程序员都无法理解的代码
  • 原文地址:https://blog.csdn.net/hotonyhui/article/details/128104021