- PriorityQueue
- 堆的应用
- 找前k个最小数据(TOPK问题)
- 求k个最小的数优化
- 堆排序
PriorityQueue
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue(优先级阻塞队列)两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的。
常用方法:
1:当你没传入比较器时候;PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常(如果放自定义类型;可以通过实现compareable接口;重写compareTo方法;传比较器优先级比较高)
class Student implements Comparable<Student>{public int age;public Student(int age) {this.age = age;}@Overridepublic int compareTo(Student o) {//return this.age - o.age;return this.age - o.age;}
}
2:不能插入null对象,否则会抛出NullPointerException
3:入和删除元素的时间复杂度为log(N)
4:PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素
5:使用时必须导入PriorityQueue所在的包,即:importjava.util.PriorityQueue;
默认是小堆;如果想要大堆;如何变成大堆?
重写compareTo方法逻辑:
@Overridepublic int compareTo(Student o) {//return this.age - o.age;return o.age - this.age;}
PriorityQueue还有其它构造方法;比如传比较器的构造方法:PriorityQueue queue = new PriorityQueue<>(Comparator<? super E> comparator);
这个构造方法创建一个空的优先级队列,并使用指定的比较器来确定元素的排序顺序。(这个使用比较器的指定顺序是比较优先的;还有一个构造方法是通过集合类和比较器构建优先级队列;那么不会用默认的比较方法;而是比较器的)
class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {//return o2-o1;return o2.compareTo(o1);}
}
PriorityQueue priorityQueue = new PriorityQueue<>(new IntCmp());
扩容机制:满了它会自动扩容的
如果容量小于64时,是按照oldCapacity的2倍方式扩容的
如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的
如果容量超过MAX ARRAY SIZE(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8),按照MAX ARRAY_SIZE来进行扩容。
在 Java 中,数组是由连续的内存空间组成的数据结构,而每个对象都会占用一定的内存空间。因此,在数组中存储对象时,需要考虑额外的内存开销。
在 PriorityQueue 中,减去 8 的目的是为了留出足够的空间,以容纳对象引用和一些额外的内部数据,如对象头信息等。这些额外的开销包括。
匿名内部类写这个代码:
PriorityQueue<Student> priorityQueue = new PriorityQueue<>(2, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o2.age - o1.age;}});
堆的应用
找前k个最小数据(TOPK问题)
最大或者最小的前k个数据。有10W个数据,让你找到前K个最大/最小的数据。
粗暴法:
// 最大或者最小的前k个数据。有10W个数据,让你找到前K个最大/最小的数据。public int[] smallstk(int []arr,int k){Arrays.sort(arr);int[] tmp=new int[k];for (int i = 0; i <k ; i++) {System.out.println(arr[i]);tmp[i]=arr[i];}return tmp;}
提问:Arrays.sort(arr);底层是什么呢?
TimSort”的混合排序算法。TimSort 是结合了归并排序和插入排序的排序算法:
TimSort 的底层细节包括以下关键步骤:
1:数据分块;输入数组被分成多个小块(或称为"run"),每个小块都是已经排好序的。
2:合并排序;TimSort 使用归并排序来合并这些小块,将它们逐步合并成较大的块,直到得到一个完全有序的数组。
3:插入排序;在合并过程中,TimSort 使用插入排序来进一步优化性能,确保排序操作尽可能高效。
小堆实现:存进堆里;然后再取出来
//使用小堆实现public int [] smallstk1(int []arr,int k){PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();for (int i = 0; i <arr.length ; i++) {priorityQueue.offer(arr[i]);}int []tmp=new int[k];for (int i = 0; i < k; i++) {tmp[i]=priorityQueue.poll();}return tmp; }
时间复杂度nlogn,第一个循环复杂度O(n)第二个循环的复杂度你nlogn;复杂度这方面确实不是很行。能否优化一下呢?
求k个最小的数优化
逻辑:1:先将最先k个元素建立大堆
2:从k后面开始就每一次往后走比较堆顶元素和数组元素大小,如果堆顶比较小就没什么事直接数组往后走。
如果堆顶比较大,就把堆顶弹出(它会自动调整为大堆),并且把这个数组元素加入进去(让它自动调整为大堆)再往后走
public static int[] smallestK3(int[] arr, int k) {//不加这句会报异常,源码if(arr == null || k == 0) {return new int[0];}//1. 建立一个大根堆PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});//2、for (int i = 0; i < arr.length; i++) {if(minHeap.size() < k) {minHeap.offer(arr[i]);}else {//当前数组的元素是arr[i] 18int val = minHeap.peek(); //27if(val > arr[i]) {//弹出大的minHeap.poll();//放进小的minHeap.offer(arr[i]);}}}//3 最把这个堆的全部元素弹给数组记录下来int[] tmp = new int[k];for (int i = 0; i < k; i++) {tmp[i] = minHeap.poll();}return tmp;}
堆排序
如果要排序一组数,从小到大(让下标有序):
使用小堆:这是不可能实现的;每次弹出最小的没有问题;但是放到哪去;如果放别的地方;空间复杂度就变大了;但是小堆,你也不一定就有序,左右谁大谁小不知道。
使用大堆:每次堆顶和最后一个交换。然后它再自动的排序好大堆。然后我们就不能包含这个最后的元素。交换位置由最后一个元素往前走一步(反之排序建立小堆)我都不用弹出处理交换,直接在原来数组交换。换完排序就好了。所以这才叫堆排序。
代码:
//堆排序public void heapSort(int []array){int usedSize=array.length;//usedSize是有效元素个数int end=usedSize-1;while (end>0){//交换0位置和最后的位置;最后的位置放最大值;每次往前走int tmp=elem[0];elem[0]=elem[end];elem[end]=tmp;shiftDown(0,end);end--;//end传的是数组元素下标,10个元素,我减1。,是不是只调整9个元素。每次结束就少一个元素调整(end--)}}
时间复杂度:建立堆的复杂度O(n)
O(n) +O(nlogn)约等于O(nlogn)
空间复杂度O(1);没有浪费,创建额外的空间