zoukankan      html  css  js  c++  java
  • 09-堆 Heap(最大堆)

    学习资源:慕课网liyubobobo老师的《玩儿转数据结构》


    1、堆

    1.1、二叉堆

    • 是一棵完全二叉树。完全二叉树:简单理解,就是把元素按从左到右的顺序,一层一层地排列成二叉树的形状

    • 有两种二叉堆:

      • 最大堆

      • 最小堆

    1.2、最大堆

    满足:堆中某个结点的值总是不大于其父结点的值

    image-20200503231333255

    1.3、最小堆

    满足:堆中某个结点的值总是不大于其孩子结点的值

    2、最大堆的实现

    可以使用数组表示一棵完全二叉树。完全二叉树逐层从左到右使用数字标记每个结点,数字对应数组中的索引。

    image-20200721170620569

    最大堆的内部使用数组实现,但对外展示还是使用完全二叉树的形式。

    2.1、实现方式

    2.1.1、方式一

    索引从 1 开始,0 空出,结点的索引有以下规律:

    • 结点 i 的父结点:parent(i) = i/2(向下取整)
    • 结点 i 的左孩子结点:leftChild(i) = 2*i
    • 结点 i 的右孩子结点:rightChild(i) = 2*i + 1
    image-20200503235120462

    2.1.2、方式二

    索引从 0 开始,结点的索引有以下规律:

    • 结点 i 的父结点:parent(i) = (i - 1)/2(向下取整)
    • 结点 i 的左孩子结点:leftChild(i) = 2*i + 1
    • 结点 i 的右孩子结点:rightChild(i) = 2*i + 2
    image-20200504000409859

    2.2、代码实现

    2.2.1、基础部分

    package heap;
    
    import array.Array;
    
    // 根结点索引从 0 开始
    public class MaxHeap<E extends Comparable<E>> {
    
    	//基于动态数组
        private Array<E> data;
    
        public MaxHeap(int capacity) {
            data = new Array<>(capacity);
        }
    
        public MaxHeap() {
            data = new Array<>();
        }
    
        public int size(){
            return data.getSize();
        }
    
        public boolean isEmpty(){
            return data.isEmpty();
        }
    
        private int parent(int index){
            
            if(index == 0){
                throw new IllegalArgumentException("根结点没有父结点");
            }
            return (index - 1)/2;
        }
    
        private int leftChild(int index){
            return index*2 + 1;
        }
    
        private int rightChild(int index){
            return index*2 + 2;
        }
    }
    

    在之前实现的动态数组中添加一个API

    // 交换两个索引处的值
    public void swap(int i, int j){
    
        if(i<0 || i>=size || j<0 || j>=size)
        	throw new IllegalArgumentException("索引越界");
    
        E e = data[i];
        data[i] = data[j];
        data[j] = e;
    }
    

    2.2.2、添加结点 和 Sift Up

    在堆的内部,元素是存储在数组中的,所以很容易实现添加操作;但是添加后操作完成之后,还要根据最大堆的性质进行调整。

    实现思路:

    1. 将新添加的结点与其父结点进行比较,如果不满足最大堆的性质,交换新添加结点和父结点的位置即可(在数组中是比较容易实现的)
    2. 重复上述比较交换的过程,直到新添加的结点小于其当前的父结点

    sift

    // 添加元素
    public void add(E e){
        
        data.addToLast(e);
        siftUp(data.getSize() - 1);
    }
    
    private void siftUp(int k) {
        
        while (k > 0 && data.get(k).compareTo(data.get(parent(k))) > 0){
            data.swap(k, parent(k));
            //继续判断交换后的新结点是否大于现在的父结点
            k = parent(k);
        }
    }
    

    2.2.3、取出元素 和 Sift Down

    对于最大堆,取出元素时只取出最大元素,即堆顶得结点,也即数组的第一个元素。

    实现思路:

    1. 取出最大元素62后,将数组中的最后一个元素16放置到堆顶
    2. 然后进行调整:新的堆顶结点16和其左右孩子结点比较,并与其中最大的孩子结点52交换位置
    3. 然后再向下不断进行,直到16这个结点到达合适的位置

    siftdown

    // 查看堆中的最大元素
    public E findMax(){
        
        if(data.getSize() == 0){
            throw new IllegalArgumentException("堆为空,没有最大结点");
        }
        return data.get(0);
    }
    // 取出堆中的最大元素
    public E exactMax(){
        
        E ret = findMax();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return ret;
    }
    private void siftDown(int k) {
        
        while(leftChild(k) < data.getSize()){
            
            // 找到结点k 的左右孩子中最大的结点
            int maxChild = leftChild(k);
            //右孩子 > 左孩子
            if(maxChild+1 < data.getSize() && data.get(maxChild+1).compareTo(data.get(maxChild)) > 0){
                maxChild = rightChild(k);
            }
            // 如果结点k已经满足最大堆,退出循环即可
            if(data.get(k).compareTo(data.get(maxChild)) >=0){
                break;
            }
            data.swap(k, maxChild);
            k = maxChild;
        }
    }
    

    2.2.4、replace

    定义:取出最大元素后,放入一个新元素;也相当于将最大元素替换为新元素

    实现思路(这里选择思路2):

    1. 可以先extractMax,再add, 两次O(logn)的操作
    2. 也可以直接将堆顶元素替换为新元素,再对堆顶元素Sift Down,一次O(logn)的操作
    public E replace(E e){
        
        E ret = findMax();
        data.set(0, e);
        siftDown(0);
        return ret;
    }
    

    2.2.5、heapify

    定义:将任意数组整理成堆的形状

    实现思路:

    1. 通过循环将数组中的元素逐个添加进堆对象中。复杂度为O(nlogn)

    2. 从最后一个非叶子结点(最后一个结点的父结点),从下之上、从右至左(对应到数组中也就是从后向前),逐个地对每一个元素元素进行Sift Down。复杂度为O(n)

    3. 直到对根结点完成 Sift Down

      heapify

    在Array类中添加新的构造器函数,传入一个静态数组转换为动态数组

    public Array(T[] arr){
        
        data = (T[])new Object[arr.length];
        for(int i=0; i<arr.length; i++){
            data[i] = arr[i];
        }
        size = arr.length;
    }
    
    public MaxHeap(T[] arr){
        
        data = new Array<>(arr);
        for (int i = parent(arr.length-1); i >=0 ; i--) {
            siftDown(i);
        }
    }
    

    3、全部代码

    package heap;
    
    import array.Array;
    
    public class MaxHeap<E extends Comparable<E>> {
    
        private Array<E> data;
    
        public MaxHeap(int capacity) {
            data = new Array<>(capacity);
        }
    
        public MaxHeap() {
            data = new Array<>();
        }
    
        public MaxHeap(E[] arr){
    
            data = new Array<>(arr);
            for (int i = parent(arr.length-1); i >=0 ; i--) {
                siftDown(i);
            }
        }
    
        public int size(){
            return data.getSize();
        }
    
        public boolean isEmpty(){
            return data.isEmpty();
        }
    
        private int parent(int index){
            if(index == 0){
                throw new IllegalArgumentException("根结点没有父结点");
            }
            return (index - 1)/2;
        }
    
        private int leftChild(int index){
            return index*2 + 1;
        }
    
        private int rightChild(int index){
            return index*2 + 2;
        }
    
        // 添加元素
        public void add(E e){
    
            data.addToLast(e);
            siftUp(data.getSize() - 1);
        }
        private void siftUp(int k) {
    
            while (k > 0 && data.get(k).compareTo(data.get(parent(k))) > 0){
    
                data.swap(k, parent(k));
                //继续判断交换后的新结点是否大于现在的父结点
                k = parent(k);
            }
        }
    
        // 查看堆中的最大元素
        public E findMax(){
    
            if(data.getSize() == 0){
                throw new IllegalArgumentException("堆为空,没有最大结点");
            }
            return data.get(0);
        }
        // 取出堆中的最大元素
        public E exactMax(){
    
            E ret = findMax();
            data.swap(0, data.getSize() - 1);
            data.removeLast();
            siftDown(0);
    
            return ret;
        }
        private void siftDown(int k) {
    
            while(leftChild(k) < data.getSize()){
    
                // 找到结点k 的左右孩子中最大的结点
                int maxChild = leftChild(k);
                //右孩子 > 左孩子
                if(maxChild+1 < data.getSize() && data.get(maxChild+1).compareTo(data.get(maxChild)) > 0){
                    maxChild = rightChild(k);
                }
    
                // 如果结点k已经满足最大堆,退出循环即可
                if(data.get(k).compareTo(data.get(maxChild)) >=0){
                    break;
                }
    
                data.swap(k, maxChild);
                k = maxChild;
            }
        }
    
        public E replace(E e){
    
            E ret = findMax();
            data.set(0, e);
            siftDown(0);
            return ret;
        }
    }
    

    4、测试

    测试Sift Down,从最大堆中取出元素,其输出必然保证有序

    @Test
    public void test(){
        
        int n = 100;
        MaxHeap<Integer> heap = new MaxHeap<>();
        Random random = new Random();
        for(int i = 0; i<n; i++){
            heap.add(random.nextInt(Integer.MAX_VALUE));
        }
        int[] arr = new int[n];
        for (int i = 0; i <n; i++) {
            arr[i] = heap.exactMax();
        }
        for (int i = 1; i < n; i++) {
    //        System.out.println(arr[i-1]);
            if(arr[i-1] < arr[i]){
                throw new RuntimeException("堆错误");
            }
        }
        System.out.println("堆正确");
    }
    

    5、其他类型的堆

    • d叉堆
    image-20200613103125202
    • 索引堆
    • 二项堆
    • 斐波那契堆

    6、基于最大堆的优先队列

    优先队列:在优先队列中,元素被赋予优先级;当访问元素时,具有最高优先级的元素最先出队。

    底层直接复用最大堆的方法实现队列的接口即可

    package heap;
    
    import queue.arrayQueue.Queue;
    
    public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
    
        private MaxHeap<E> maxHeap;
    
        public PriorityQueue() {
            this.maxHeap = new MaxHeap<>();
        }
    
        @Override
        public void enqueue(E e) {
            maxHeap.add(e);
        }
    
        @Override
        public E dequeue() {
            return maxHeap.exactMax();
        }
    
        @Override
        public E getFront() {
            return maxHeap.getMax();
        }
    
        @Override
        public int getSize() {
            return maxHeap.size();
        }
    
        @Override
        public boolean isEmpty() {
            return maxHeap.isEmpty();
        }
    }
    

    7、Java中的优先队列

    PriorityQueue,底层是最小堆

    特点:构造器可以传入一个比较器,规定优先级

    public class PriorityQueue<E> extends AbstractQueue<E>
        implements java.io.Serializable {
        
    }
    

    API接口

    image-20200613101805246
  • 相关阅读:
    hdu1124 Factorial (求解一个数的阶乘中出现多少个0)
    SQL on Linux: Erro Unable to read instance id from /var/opt/mssql/.system/instance_id
    Error during WebSocket handshake: Unexpected response code: 200 问题处理
    CUP计算资源争抢通过IIS启用处理器关联解决
    ABP在MultipleDbContext也就是多库的场景下发布后异常“Could not find content root folder”问题处理
    ABP运行Login failed for user 'IIS APPPOOL XXXXX Reason: Could not find a login matching the name provided问题解决
    vs2017cpu占用过高解决方案
    docker查看挂载目录Volume
    windows 10安装docker一直挂起在Installing Components and Removing Files
    ABP vue+asp.net core yarn serve报 Cannot find module 'typescript/package.json错误
  • 原文地址:https://www.cnblogs.com/sout-ch233/p/13111965.html
Copyright © 2011-2022 走看看