堆是如何实现的

263 阅读3分钟

一、堆

堆是一种特殊的树。什么样的树才是堆?需要满足两点

  • 堆是一个完全二叉树
  • 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值

第一点,完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。

完全二叉树.drawio.png

首先需要明确的是,完全二叉树适用用数组来存储,以达到节省空间的目的。因为不需要存储左右子节点的指针,通过数组的小标即可找到一个节点的左右子节点和父节点。也就是,堆的存储方式是线性表。

对于每个节点的值都大于等于子树中每个节点值的堆,叫做“大顶堆”,对于每个节点值都小于等于树中每个节点值的值,叫做“小顶堆”。

堆在数组中是如何表示的?

数组中下标为i的节点的左子节点,就是下标为i*2的节点,右子节点就是下标为 i* 2+1的节点,父节点就是下标为i/2的节点。

堆上的操作有哪些?

  1. 往堆中插入一个元素

    往堆中插入一个元素后,需要调整堆让其重新满足堆的特性。这个过程也称为调整堆(heapify)。

    调整堆有两种方式:从下往上和从上往下

    先来看看从下往上:把新插入的元素放在堆的最后,一直交换元素直到满足堆的特性。

插入元素1.drawio.png

顺着节点所在路径,向上比较,然后交换。

插入元素2.drawio.png

可以看到,到这一步,不再需要交换,堆调整结束。

  1. 删除堆顶元素

    堆顶元素是堆中数据的最大值或者最小值。假设构造的是大顶堆,删除堆顶元素之后,就需要把第二大的元素放到堆顶,从左右子节点中选择较大节点,迭代此过程,直到叶子节点被删除。

    如果采用从下往上的方式,会出现什么问题?(假设删除堆顶元素35)

堆调整的空洞.drawio.png

可以看到,可能会存在空洞,也就是不满足堆线性存储节省空间的要求了。

所以需要采用从上往下的堆化方法。也就是直接把堆的最后一个元素放到堆顶,然后执行堆调整的过程。因为移除的是数组中的最后 一个元素,而在堆化的过程中,都是交换操作,不会出现“空洞”。

自上往下.drawio.png

往堆中插入元素和删除堆顶元素的代码实现如下:

public class Heap {
    private int[] heap;
    private int count;//堆存储的元素
    private int n;//堆的大小
​
    public Heap(int capacity){
        heap = new int[capacity+1];//从下标1开始存储元素
        this.n=capacity;
        this.count=0;
    }
​
    //往堆中插入一个元素
    public void insert(int num){
        //堆已满
        if(count>=n)
            return;
        //从1开始放,count先++
        count++;
        //直接放到数组末尾
        heap[count]=num;
        int i=count;
        while(i/2>0&&heap[i]>heap[i/2]){
            swap(heap,i,i/2);
            //更新i
            i=i/2;
        }
​
    }
​
    public void removeMax(){
        //数组为空
        if(count==0)
            return;
        heap[1]=heap[count];
        //更新count
        count--;
        heapify(count,1);
​
    }
​
    public void heapify(int n,int i) {
        int maxPos = i;
        while (true) {
            //从左右节点中挑一个大的,交换位置
            if (i * 2 <= n && heap[i] < heap[i * 2])
                maxPos = i * 2;
            if (i * 2 + 1 <= n && heap[maxPos] < heap[i * 2 + 1])
                maxPos = i * 2 + 1;
            //说明maxPos没有发生改变,即当前位置是正确位置,停止交换
            if (maxPos == i)
                break;
            //交换父节点和较大节点的值
            swap(heap, maxPos, i);
            //更新i
            i = maxPos;
        }
    }
​
    public int getMax(){
        if(count==0)
            return -1;
        else return heap[1];
    }
}