一、堆
堆是一种特殊的树。什么样的树才是堆?需要满足两点
- 堆是一个完全二叉树
- 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值
第一点,完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。
首先需要明确的是,完全二叉树适用用数组来存储,以达到节省空间的目的。因为不需要存储左右子节点的指针,通过数组的小标即可找到一个节点的左右子节点和父节点。也就是,堆的存储方式是线性表。
对于每个节点的值都大于等于子树中每个节点值的堆,叫做“大顶堆”,对于每个节点值都小于等于树中每个节点值的值,叫做“小顶堆”。
堆在数组中是如何表示的?
数组中下标为i的节点的左子节点,就是下标为i*2的节点,右子节点就是下标为 i* 2+1的节点,父节点就是下标为i/2的节点。
堆上的操作有哪些?
-
往堆中插入一个元素
往堆中插入一个元素后,需要调整堆让其重新满足堆的特性。这个过程也称为调整堆(heapify)。
调整堆有两种方式:从下往上和从上往下。
先来看看从下往上:把新插入的元素放在堆的最后,一直交换元素直到满足堆的特性。
顺着节点所在路径,向上比较,然后交换。
可以看到,到这一步,不再需要交换,堆调整结束。
-
删除堆顶元素
堆顶元素是堆中数据的最大值或者最小值。假设构造的是大顶堆,删除堆顶元素之后,就需要把第二大的元素放到堆顶,从左右子节点中选择较大节点,迭代此过程,直到叶子节点被删除。
如果采用从下往上的方式,会出现什么问题?(假设删除堆顶元素35)
可以看到,可能会存在空洞,也就是不满足堆线性存储节省空间的要求了。
所以需要采用从上往下的堆化方法。也就是直接把堆的最后一个元素放到堆顶,然后执行堆调整的过程。因为移除的是数组中的最后 一个元素,而在堆化的过程中,都是交换操作,不会出现“空洞”。
往堆中插入元素和删除堆顶元素的代码实现如下:
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];
}
}