一、Heap的定义
堆就是可以迅速找到一堆数中的最大值或者最小值的数据结构,所以堆可以用来实现优先队列。在java中PriorityQueue的底层用到了堆。
二、Heap的特点
-
将根节点最大的堆叫大顶堆或者大根堆,根节点最小的堆叫小顶堆或小根堆。
-
常见的堆有
二叉堆,斐波拉契堆。 -
如果是大顶堆,常见操作及时间复杂度: 查找最大值:o(1)
删除最大值:o(logn)
添加值:o(1)或o(logn)
三、二叉堆
- 二叉堆实现相对容易,相对时间复杂度也高。
- 时间复杂度最低的实现方式是严格的斐波拉契。
1.二叉堆的性质
- 二叉堆是一棵
完全树 - 树中的任意节点的值总是>=其子节点的值
- 二插堆一般用
数组实现。
如果第一个元素在数组中索引为0
索引为i的父节点索引为(i-1)/2
索引为i的左子节点索引为2i+1
索引为i的右子节点索引为2i+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;
}