堆
概述
在数据结构中,堆(Heap)是一种特殊的树形数据结构,通常是一个完全二叉树(Complete Binary Tree),满足以下性质:
-
堆序性:
- 对于大顶堆(Max Heap),每个节点的值都大于或等于其子节点的值。即对于任意节点i,
heap[i] >= heap[2i+1]
且heap[i] >= heap[2i+2]
。 - 对于小顶堆(Min Heap),每个节点的值都小于或等于其子节点的值。即对于任意节点i,
heap[i] <= heap[2i+1]
且heap[i] <= heap[2i+2]
。
- 对于大顶堆(Max Heap),每个节点的值都大于或等于其子节点的值。即对于任意节点i,
-
完全二叉树特性:
- 堆是一棵完全二叉树,这意味着除了最后一层之外,所有层级都被填满,并且最后一层的所有节点尽可能地靠左排列。
-
存储方式:
- 在实际实现中,堆通常以数组的方式存储,利用完全二叉树的特点可以节省空间。根节点位于数组的起始位置(索引为0或1取决于具体的实现),子节点与其父节点在数组中的位置存在固定的关系。
由于堆的这些特性,它常用于实现优先队列和求解一些排序问题,例如堆排序算法。
代码
- 构建堆函数heapify() : void
- 元素下沉函数down(parent : int) : void
- 元素上浮函数up(offered : int, index : int) : void
import java.util.Arrays;
/**
* 可以扩容的 heap, max 用于指定是大顶堆还是小顶堆
*/
public class Heap {
int[] array;
int size;
boolean max;
public int size() {
return size;
}
public Heap(int capacity, boolean max) {
this.array = new int[capacity];
this.max = max;
}
/**
* 获取堆顶元素
*
* @return 堆顶元素
*/
public int peek() {
return array[0];
}
/**
* 删除堆顶元素
*
* @return 堆顶元素
*/
public int poll() {
int top = array[0];
swap(0, size - 1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
*
* @param index 索引
* @return 被删除元素
*/
public int poll(int index) {
int deleted = array[index];
up(Integer.MAX_VALUE, index);
poll();
return deleted;
}
/**
* 替换堆顶元素
*
* @param replaced 新元素
*/
public void replace(int replaced) {
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
*
* @param offered 新元素
*/
public void offer(int offered) {
if (size == array.length) {
// 扩容
grow();
}
up(offered, size);
size++;
}
private void grow() {
int capacity = size + (size >> 1);
int[] newArray = new int[capacity];
System.arraycopy(array, 0,
newArray, 0, size);
array = newArray;
}
// 将 offered 元素上浮: 直至 offered 小于父元素或到堆顶
private void up(int offered, int index) {
int child = index;
while (child > 0) {
int parent = (child - 1) / 2;
boolean cmp = max ? offered > array[parent] : offered < array[parent];
if (cmp) {
array[child] = array[parent];
} else {
break;
}
child = parent;
}
array[child] = offered;
}
public Heap(int[] array, boolean max) {
this.array = array;
this.size = array.length;
this.max = max;
heapify();
}
// 建堆
private void heapify() {
// 如何找到最后这个非叶子节点 size / 2 - 1
for (int i = size / 2 - 1; i >= 0; i--) {
down(i);
}
}
// 将 parent 索引处的元素下潜: 与两个孩子较大者交换, 直至没孩子或孩子没它大
private void down(int parent) {
int left = parent * 2 + 1;
int right = left + 1;
int maxOrMin = parent;
if (left < size && (max ? array[left] > array[maxOrMin] : array[left] < array[maxOrMin])) {
maxOrMin = left;
}
if (right < size && (max ? array[right] > array[maxOrMin] : array[right] < array[maxOrMin])) {
maxOrMin = right;
}
if (maxOrMin != parent) { // 找到了更大的孩子
swap(maxOrMin, parent);
down(maxOrMin);
}
}
// 交换两个索引处的元素
private void swap(int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
/*
100
/ \
10 99
/ \ / \
5 6 98 97
/\ /\ /
1 2 3 4 96
100
/ \
96 99
/ \ / \
10 6 98 97
/\ /\
1 2 3 4
*/
public static void main(String[] args) {
Heap heap = new Heap(5, true); //100,10,99,5,6,98,97,1,2,3,4,96
heap.offer(100);
heap.offer(10);
heap.offer(99);
heap.offer(5);
heap.offer(6);
heap.offer(98);
heap.offer(97);
heap.offer(1);
heap.offer(2);
heap.offer(3);
heap.offer(4);
heap.offer(96);
System.out.println(Arrays.toString(heap.array));
System.out.println(heap.size);
System.out.println(heap.poll(3));
System.out.println(Arrays.toString(heap.array));
System.out.println(heap. Size);
}
}
力扣题目
- 215. 数组中的第K个最大元素 (构建容量为k的小顶堆解决,顶部即为数组中第K个最大元素)
- 703. 数据流中的第 K 大元素
- 295. 数据流的中位数
来源
路漫漫其修远兮,吾将上下而求索。