数据结构-堆

526 阅读4分钟

一、Heap的定义

堆就是可以迅速找到一堆数中的最大值或者最小值的数据结构,所以堆可以用来实现优先队列。在java中PriorityQueue的底层用到了堆。

二、Heap的特点

  • 将根节点最大的堆叫大顶堆或者大根堆,根节点最小的堆叫小顶堆或小根堆。

  • 常见的堆有二叉堆,斐波拉契堆。

  • 如果是大顶堆,常见操作及时间复杂度: 查找最大值:o(1)
    删除最大值:o(logn)
    添加值:o(1)或o(logn)

三、二叉堆

  • 二叉堆实现相对容易,相对时间复杂度也高。
  • 时间复杂度最低的实现方式是严格的斐波拉契。

在这里插入图片描述

1.二叉堆的性质

  • 二叉堆是一棵完全树
  • 树中的任意节点的值总是>=其子节点的值
  • 二插堆一般用数组实现。

如果第一个元素在数组中索引为0
索引为i的父节点索引为(i-1)/2
索引为i的左子节点索引为2i+1
索引为i的右子节点索引为2
i+2

二叉堆对应数组 在这里插入图片描述

2.二叉堆的添加

假设是大顶堆

  • 判断堆中是否有值,如果还没有,直接添加
  • 如果有值,添加到数组的尾部
  • 进行堆的向上维护调整:
    1.插入的值和父节点比较,如果比父节点小,说明就是这个位置。插入成功,时间复杂度为o(1)
    2.如果要插入的值比父节点大。那就需要和父节点换位置,直到到达目的地,时间复杂度最坏为o(logn),也就是换到了堆顶。

实现图 在这里插入图片描述

3.二叉堆顶的删除

假设是大顶堆

  • 将堆尾部的元素覆盖堆顶。
  • 堆的长度减一
  • 进行堆向下维护调整
    1.比较堆顶当前节点的左右子节点,找到值更大的那一个。
    2.值更大的子节点和堆顶当前的值交换,直到当前节点的值大于左右子节点的值。

实现图 在这里插入图片描述

4.二叉堆的代码实现(详解)

import java.util.Arrays;
import java.util.NoSuchElementException;

/**
 * @author 朱雨鹏
 * @create 2020-12-12 12:33
 */
public class BinaryHeap {
    // d代表树的分支,这里实现二叉堆。
    private static final int d = 2;
    private int[] heap;
    private int heapSize;

    /**
     * 初始化二叉堆
     */
    public BinaryHeap(int capacity) {
        // 数组下标从0开始
        heapSize = 0;
        // 末尾多一位为空,方便添加操作
        heap = new int[capacity + 1];
        // 数组初始化赋值
        Arrays.fill(heap, -1);
    }

    // 判断是否为空
    public boolean isEmpty() {
        return heapSize == 0;
    }

    // 判断是否为满
    public boolean isFull() {
        return heapSize == heap.length;
    }

    // 返回i节点下标的父节点下标,数组的下标是从0开始的。
    private int parent(int i) {
        return (i - 1) / d;
    }

    /**
     * 查找堆顶元素
     * 复杂度: O(1)
     */
    public int findMax() {
        if (isEmpty())
            throw new NoSuchElementException("堆为空");
        return heap[0];
    }

    /**
     * 添加
     * 时间复杂度: O(log N)
     */
    public void insert(int x) {
        if (isFull()) {
            throw new NoSuchElementException("堆已经满啦!");
        }
        // 加入堆尾
        heap[heapSize] = x;
        // 堆的已有长度+1
        heapSize++;
        // 向上调整(维护堆结构)
        heapifyUp(heapSize - 1);
    }

    /**
     * 删除堆中下标为x的元素
     * 时间复杂度: O(log N)
     */
    public int delete(int x) {
        if (isEmpty()) {
            throw new NoSuchElementException("堆为空!");
        }
        // 保存待删除元素
        int maxElement = heap[x];
        // 用末尾的元素覆盖要删除的元素
        heap[x] = heap[heapSize - 1];
        // 末尾元素减一
        heapSize--;
        // 向下调整(维护堆结构)
        heapifyDown(x);
        return maxElement;
    }


    /**
     * 当插入元素后维护堆结构(向上交换进行维护)
     */
    private void heapifyUp(int i) {
        // 得到最后的插入值
        int insertValue = heap[i];
        // 维护堆(如果子下标的值大于父下标,则它们进行交换。)
        while (i > 0 && insertValue > heap[parent(i)]) {
            heap[i] = heap[parent(i)];
            i = parent(i);
        }
        heap[i] = insertValue;
    }

    // 返回左右子下标中较大的下标
    private int maxChild(int i) {
        int leftChild = (d * i + 1);
        int rightChild = (d * i + 2);
        return heap[leftChild] > heap[rightChild] ? leftChild : rightChild;
    }

    /**
     * 当插入元素后维护堆结构(向下交换进行维护)
     */
    private void heapifyDown(int i) {
        int child;
        // 保存要交换前i下标对应的值
        int temp = heap[i];
        // 当数组未越界,维护堆(如果左右子下标中较大的子下标的值大于父下标,则它们进行交换。)
        while ((d * i + 1) < heapSize) {
            child = maxChild(i);
            if (temp >= heap[child]) {
                break;
            }
            heap[i] = heap[child];
            i = child;
        }
        heap[i] = temp;
    }