注: 本文章都是以 大根堆 的形式来存储,存储数据都是 [1,13,4,25,6,3],编程语言是 java
什么是堆
堆是以 完全二叉树 的形式存储,它分为 大根堆 与 小根堆
大根堆: 每一颗树的父节点都比子节点都要 大
小根堆: 每一颗树的父节点都比子节点都要 **小**
堆的创建
首先,在创建之前需要了解几个公式,其中 root 表示父节点的下标, childLeft 表示左子树的下标, childRight 表示右子树的下标,child 表示左/右子树的下标
- root = (child - 1) / 2
- childLeft = root * 2 + 1
- childRight = root * 2 + 2
在创建之前,先要提一下 向下位移, 如果了解了这个,那么堆一半的内容就基本会了
向下筛选
/**
* 应用的场景:
* 1. 创建堆
* 2. 删除数据
* 思路:
* 1. 找到子树中的较大者
* 2. 将较大者与父节点的值经行比较
* 3. 如果父节点的值比较大就退出,
* 4. 如果较小,那么交换,然后再对已经交换后的子树重复如上的操作
* @param root 是每棵子树的根节点的下标
* @param len 是每棵子树调整结束的结束条件
* 向下调整的时间复杂度:O(logn)
*/
private void siftDown(int root,int len) {
int child = root * 2 + 1;
while(child < len) {
if(child + 1 < len && elem[child] < elem[child + 1]) {
// 有右树并且右树的元素比左边大
child++;
}
// 此时 child 一定是较大着的下标
if (elem[child] > elem[root]) {
swap(child, root); // 交换数据
root = child;
child = root * 2 + 1;
} else {
break;
}
}
}
创建
/**
* 建堆的时间复杂度:O(N)
* 从最后一个非叶子节点的下标部开始创建,直到到下标 0
* 为什么?
* 因为如果从下标 0 开始创建,一次遍历完还不能创建好
* 怎么找到 最后一个非叶子节点
* 因为它是完全二叉树 由公式 root = (child - 1) / 2 可知
* 最后一个叶子节点的下标是 (array.length - 1) / 2
* @param array
*/
public void createHeap(int[] array) {
elem = Arrays.copyOf(array, array.length); // 克隆
usedSize = array.length;
int child = array.length;
// 从最后一个非叶子节点的下标部开始创建
for (int i = (child - 1) / 2; i >= 0; i--) {
siftDown(i, array.length);// 向下位移
}
}
删除
/**
* 思路:
* 1. 将第一个元素与最后一个元素调换
* 2. 然后再用 向下位移 将其重新变为大根堆
* 出队【删除】:每次删除的都是优先级高的元素
* 仍然要保持是 大根堆
*/
public void pollHeap() {
if(isEmpty()) {
return;
}
swap(0, usedSize - 1);
usedSize--;
siftDown(0, usedSize);
}
添加
由于添加在末尾,所以不适合用 向下位移,所以就需要用到 向上位移
向上筛选
/**
* 应用场景: 添加
* 因为是向上位移 所以传入的是 要位移的子树的下标
* 思路与向下位移类似,所以就不说了
*/
private void siftUp(int child) {
int root = (child - 1) / 2; // 得到父节点下标
while(child > 0) {
if(elem[child] > elem[root]) {
swap(child, root); // 交换
child = root;
root = (child - 1) / 2;
} else {
break;
}
}
}
添加
/**
* 入队:仍然要保持是大根堆
* @param val
*/
public void push(int val) {
if(isFull()) {
elem = Arrays.copyOf(elem, elem.length + (elem.length >>> 1)); // 增大为原来的1.5倍
}
elem[usedSize] = val;
siftUp(usedSize);
usedSize++;
}
所有的代码
import java.util.Arrays;
public class PriorityQueue {
public int[] elem;
public int usedSize;
public PriorityQueue() {
elem = new int[10];
}
/**
* 建堆的时间复杂度:O(N)
*
* @param array
*/
public void createHeap(int[] array) {
elem = Arrays.copyOf(array, array.length);
usedSize = array.length;
int child = array.length;
for (int i = (child - 1) / 2; i >= 0; i--) {
siftDown(i, array.length);
}
}
/**
*
* @param root 是每棵子树的根节点的下标
* @param len 是每棵子树调整结束的结束条件
* 向下调整的时间复杂度:O(logn)
*/
private void siftDown(int root,int len) {
int child = root * 2 + 1;
while(child < len) {
if(child + 1 < len && elem[child] < elem[child + 1]) {
// 有右树并且右树的元素比左边大
child++;
}
// 此时 child 一定是较大着的下标
if (elem[child] > elem[root]) {
swap(child, root);
root = child;
child = root * 2 + 1;
} else {
break;
}
}
}
private void swap(int i, int j) {
int t = elem[i];
elem[i] = elem[j];
elem[j] = t;
}
/**
* 入队:仍然要保持是大根堆
* @param val
*/
public void push(int val) {
if(isFull()) {
elem = Arrays.copyOf(elem, elem.length + (elem.length >>> 1));
}
elem[usedSize] = val;
siftUp(usedSize);
usedSize++;
}
private void siftUp(int child) {
int root = (child - 1) / 2;
while(child > 0) {
if(elem[child] > elem[root]) {
swap(child, root);
child = root;
root = (child - 1) / 2;
} else {
break;
}
}
}
public boolean isFull() {
return usedSize == elem.length;
}
/**
* 出队【删除】:每次删除的都是优先级高的元素
* 仍然要保持是 大根堆
*/
public void pollHeap() {
if(isEmpty()) {
return;
}
swap(0, usedSize - 1);
usedSize--;
siftDown(0, usedSize);
}
public boolean isEmpty() {
return usedSize == 0;
}
/**
* 获取堆顶元素
* @return
*/
public int peekHeap() {
return elem[0];
}
}
java中的优先级队列 PriorityQueue
创建
// 采用 lambda表达式写比较器 创建大根堆
PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));
// 采用 匿名内部类创建 创建大根堆
PriorityQueue<Integer> queue1 = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
// 采用默认模式创建 --> 创建小根堆
PriorityQueue<Integer> queue1 = new PriorityQueue<>();
功能
方法名 | 作用 |
---|---|
offer(E val) | 添加 val |
peek() | 查看但不删除 |
poll() | 查看并删除 |