1.堆
底层是一个数组,是一个完全二叉树按顺序存储的结果,对于非完全二叉树,则不必要用顺序存储的方法来存储,否则会有很多空间浪费。在来了解堆之前需要先对完全二叉树有一定的了解,才能来进一步深入地了解堆。
(1)分类:
有大根堆和小根堆之分。大根堆:根节点的val比左右孩子节点的val都要大;小根堆:根节点的val比左右孩子节点的val都要小。每一棵子树都是如此。
(2)如何创建一个堆:
eg:这里以创建大根堆为例子。
这样以后我们就创建好了一个大根堆。
代码实现:
public class MyHeap {
public int[] arr;
public int usedSize;
public MyHeap() {
this.arr = new int[10];
}
public void initArr(int[] array) {
for(int i = 0; i < array.length; i++) {
arr[i] = array[i];
usedSize++;//别忘了
}
}
public void createMaxHeap() {
for(int parent = (arr.length - 1 - 1) / 2 ; parent >= 0; parent--) {
//从最后一棵子树开始,使用向下调整
softDown(parent, this.usedSize);
}
}
//向下调整:时间复杂度:O(logn)
private void softDown(int parent, int usedSize) {
//计算左子树
int child = parent * 2 + 1;
while(child < usedSize) {
if(child + 1 < usedSize && arr[child] < arr[child + 1]) {
child++;
}
//如果是小根堆把这里的大于改成小于就好了
if(arr[child] > arr[parent]) {
//交换
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
parent = child;
child = parent * 2 + 1;
}else {
break;
}
}
}
}
(3)堆的增,删,查操作:
a.增:(Offer)
添加元素的具体操作是在数组末尾添加进去,并且进行向上调整(与向下调整有异曲同工之处)
分析:
代码实现:
private void softUp(int child) {
int parent = (child - 1) / 2;
while(child != parent) {
if(arr[child] > arr[parent]) {
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
child = parent;
parent = (child - 1) / 2;
}else {
break;
}
}
}
private boolean isFull() {
return usedSize == arr.length;
}
public void offer(int val) {
if(isFull()) {
arr = Arrays.copyOf(arr,usedSize * 2);
}
arr[usedSize] = val;
softUp(usedSize);
usedSize++;
}
b.删
删除元素删的是堆的堆顶元素,具体操作是首先交换第一个元素与堆(数组)的最后一个元素,然后再进行向下调整(调整的是0下标的那棵树),注意此时堆的长度要减1。
代码实现:
private boolean isEmpty() {
return usedSize == 0;
}
public int poll() {
if(isEmpty()) {
throw new RuntimeException("堆为空!");
}
int ret = arr[0];
swap(arr,0,usedSize);
usedSize--;
softDown(0,usedSize);
return ret;
}
c.查
查看元素就是获取堆的堆顶元素,具体操作是直接返回数组的首元数。
2.优先级队列
优先级队列就是基于堆来实现的。Java原生库中默认是小根堆(添加,删除元素都是按照小根堆的方式来调整整棵树的),这个小根堆是可以改变的。
(1)构造方法:
(2)常用方法:
注意:
a.优先级队列这个类中没有实现empty方法,所以来判断是否为空只有一种方法,就是isEmpty。
b.往队列中插入的元素必须可以比较,否则会抛出ClassCastException。
c.不能往队列中插入null,否则会抛出NullPointerException。
d.删除和插入的时间复杂度都是。
f.此处的优先级队列是线程不安全的,PriorityBlockingQueue是线程安全的。
g.使用前需要导入包java.util.PriorityQueue
(3)TopK问题:
返回前K个最小的数。这里提供两种方法:
法一:把所有的数都插入到队列,按照小根堆的方式,然后再出K次队列。时间复杂度为:
法二:把K个个数的数据建成大根堆,然后从第K个元素开始往队列插入,前提条件是要比堆顶元数要小,这样就能保证这个这个大根堆里就是前K个最小的数。实际做的时候也是选用这种方法,不选用法一。时间复杂度为:
代码实现:
import java.util.PriorityQueue;
//建大根堆需要传入比较器
class maxHeap implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
}
class Solution {
public int[] smallestK(int[] arr, int k) {
int[] ret = new int[k];
if(arr.length == 0 || k == 0) {
return ret;
}
PriorityQueue<Integer> queue = new PriorityQueue<>(k,new maxHeap());
//对前k个元素建立大根堆
for(int i = 0; i < k; i++) {
queue.offer(arr[i]);
}
for(int j = k; j < arr.length; j++) {
if(queue.peek() > arr[j]) {
//出队列
queue.poll();
queue.offer(arr[j]);
}
}
for(int i = ret.length - 1; i >= 0; i--) {
ret[i] = queue.poll();
}
return ret;
}
}