堆(优先队列)
1.概述
堆是一颗完全二叉树
一般都是用数组
来模拟堆,堆中的索引满足下面的关系,假设一个节点的索引为x
,那么它的左儿子节点为 2 * x
右儿子节点索引为 2 * x + 1
,所以为了方便操作一般根节点的索引都是从1开始
大根堆与小根堆
为何堆又称为优先队列,因为我们根据某个条件将堆中最符合条件的值动态调整到根节点的位置
,所以堆可以用来排序,我们调整每个父节点的值都小于或者等于两个儿子节点
所以根节点的值就是堆中的最小值,这就是小根堆
,
大根堆顾名思义,每个父节点的值都大于或者等于两个儿子节点的值
2.实现堆排序
堆排序是不稳定的
AcWing838.堆排序 ——>题解
输入一个长度为 n的整数数列,从小到大输出前 m 小的数。
我们使用堆排序,先构造一个堆,每次总是满足根节点的值是最小的(down方法)
,然后让其出队(删除),然后将最后一个节点的值赋给根节点,然后再次调整使其最小且堆中的所有节点都满足小于等于儿子节点,(这个就是删除头结点的做法)
2.1实现down方法
down方法就是调整某一个节点,使其满足大于(小于)或者等于
两个儿子节点的值
如下图所示,7这个节点显然是不满足小于两个儿子节点的是,所以与两个儿子节点比较值的大小,然后调整其位置,让其与值为7的节点交换后满足条件
down方法实现
由于我们要满足删除头节点后堆中所有的节点还都满足小于两个儿子节点,所有我们要从上到下调整,我们可以用递归和迭代两种方式做。
迭代版
//down方法 传来的是父节点的索引 迭代版
private static void downIter(int x){
int t = x;
while(true){
//判断左右两个儿子节点的值是否小于父节点
if(2 * x <= size && heap[2 * x] < heap[t]) t = 2 * x;
if(2 * x + 1 <= size && heap[2 * x + 1] < heap[t]) t = 2 * x + 1;
//最小值的索引不是父节点索引 说明得调整
if(t != x){
int temp = heap[t];
heap[t] = heap[x];
heap[x] = temp;
x = t;
}
//最小值就是自己 往下就不用比较了 直接break了
else break;
}
}
递归版
//down函数 递归版
private static void downRecu(int x){
int t = x;
if(2 * x <= size && heap[2 * x] < heap[t]) t = 2 * x;
if(2 * x + 1 <= size && heap[2 * x + 1] < heap[t]) t = 2 * x + 1;
if(t != x){
int temp = heap[t];
heap[t] = heap[x];
heap[x] = temp;
//递归做
downRecu(t);
}
}
2.2构造堆
这里构造堆有技巧,因为最后一层没有子节点,我们可以直接从倒数第二层的最后一个元素开始down,一直down到根节点 时间复杂度是O(n)
import java.util.Scanner;
public class Main{
//数组 模拟堆
private static int[] heap = new int[100010];
//元素个数 也代表索引
private static int size;
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
//先将所有的元素读到数组中
for(int i = 1; i <= n; i ++ )heap[i] = in.nextInt();
size = n;
//构造堆 从倒数第二层开始down
for(int i = n / 2; i != 0; i --) downIter(i);
//此时的堆就是一个小根堆 所有的父节点都小于或者等于儿子节点
//输出前m个最小值
while(m -- > 0 ){
//直接输出根节点的值就是最小值
System.out.print(heap[1] + " ");
//删除根节点 将最后一个节点值赋给根节点,然后down根节点
heap[1] = heap[size--];
downIter(1);
}
}
}
2.3额外实现up操作
将一个节点向上调整
即当插入一个数时,这个数首先在最后,为了保证特性,这时必须向上调整
void up(int x ){
//跟它的父节点比较
while(x / 2 != 0 && heap[x / 2] > heap[x]){
//交换heap[x] heap[x / 2]
swap(heap[x],heap[x / 2]);
//继续向上
x / = 2;
}
}
4.Java中的PriorityQueue
Java中的优先队列底层是一个Object类型的数组
,默认初始化容量是11
,当第一次插入元素时才会创建这个数组对象
transient Object[] queue;
构造器
常用的一个构造器,可以传入一个比较器,作用就是指定优先的规则
public PriorityQueue(Comparator<? super E> comparator)
常用方法
//添加 add底层就是offer
public boolean add(E e)
public boolean offer(E e)
//删除某个元素
public boolean remove(Object o)
//查看队头元素 element底层调的就是peek 只是队头是null报异常
public E element()
public E peek()
//队头元素出队 (重新调整堆)
public E poll() {
//是否包含某一个元素
public boolean contains(Object o)
//判断是否一个存在一个集合的元素
public boolean containsAll(Collection<?> c) {
//清空队列
public void clear() {
//转成一个指定类型的数组
public <T> T[] toArray(T[] a)