堆(heap)
什么是堆
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一颗树(逻辑上)的数组对象(实质上)。并且堆总是满足下列性质:
- 堆中某个结点的值总是不大于或不小于其父节点的值
- 堆总是一颗完全二叉树
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
那么对应的已知父节点下标parentIndex,则左孩子的下标为2*parentIndex+1,右孩子的下标为2*parentIndex+2;
堆的作用是快速找集合中的最值,例如平时写的题目topK即可用堆来写。
重要操作-向下调整
前提:左右子树必须已经是一个堆,才能调整。
还是以小堆为例:
那么问题来了,问题一:怎么判断index对应的位置是不是叶子结点?
回答:因为它在逻辑上是一颗完全二叉树,如果一个结点没有左孩子,那么它一定没有右孩子,所以需判断2*index+1和size的大小关系,如果前者小于size则一定有,反之没有。
问题二:找到最小的孩子中最小的(一定有左孩子)
最小的为右孩子得满足有右孩子并且右孩子比较小才可。
代码实现:
public class Heap {
public static void adjustDown(int[] array,int size,int index){
int leftIndex = 2*index + 1;
//1.判断index是否为叶子结点
while(leftIndex < size) {
//2.找最小的孩子
int minIndex = leftIndex;
int rightIndex = leftIndex + 1;
if (rightIndex < size && array[rightIndex] < array[leftIndex]) {
minIndex = rightIndex;
}
//3.比较最小孩子的值和index位置的值
if (array[index] <= array[minIndex]) {
return;
}
//4.交换
int t = array[index];
array[index] = array[minIndex];
array[minIndex] = t;
//5.把最小的孩子视为index,继续循环
index = minIndex;
leftIndex = 2 * index + 1;
}
}
}
建堆
public static void createHeap(int[] array,int size){
//找到层序遍历的最后一个节点下标
int lastIndex = size - 1;
//找到最后一个节点的父节点的下标
int lastParentIndex = (lastIndex - 1)/2;
//从最后一个父节点一直向下调整,直到调整到第一个节点结束
for(int i = lastParentIndex;i >= 0;i--){
adjustDown(array,size,i);
}
}
堆的应用
优先级队列
优先级队列在Java中的api->(java.util.PriorityQueue implement Queue),它的底层实际上就是用堆来实现的,那么我们现在自己来实现一下。
- 首先需要一个数组,这里为了简单方便所以使用固定数组大小100,以及size。
- 查看元素,即查看堆顶元素(数组首元素)。
- 删除元素,先将堆的最后一个元素(数组最后一个元素)放到首元素位置,再进行向下调整,因为这样它的左子树和右子树都满足堆,所以只需要调整一次即可,即log(n)
- 插入元素,将需要插入的元素尾插进去数组,然后对它进行向上调整。
public class MyPriorityQueue {
private Integer[] array;
private int size;
public MyPriorityQueue(){
//简单起见,不考虑扩容
array = new Integer[100];
size = 0;
}
public Integer element(){
if(size == 0){
throw new RuntimeException("空了");
}
return array[0];
}
public Integer remove(){
if(size == 0){
throw new RuntimeException("空了");
}
int e = array[0];
array[0] = array[size - 1];
size--;
adjustDown(0);
return e;
}
//log(n)
public void add(Integer e){
array[size++] = e;
adjustUp(size-1);
}
public void adjustUp(int index){
while(index > 0){
int parentIndex = (index - 1)/2;
if(array[parentIndex] <= array[index]){
return;
}
int t = array[parentIndex];
array[parentIndex] = array[index];
array[index] = t;
index = parentIndex;
parentIndex = (index - 1)/2;
}
}
public void adjustDown(int index){
int leftIndex = index * 2 + 1;
while(leftIndex < size){
int minIndex = leftIndex;
int rightIndex = leftIndex + 1;
if(rightIndex < size && array[rightIndex] < array[leftIndex]){
minIndex = rightIndex;
}
if(array[minIndex] >= array[index]){
return;
}
int t = array[minIndex];
array[minIndex] = array[index];
array[index] = t;
index = minIndex;
leftIndex = index * 2 + 1;
}
}
}