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;
}
@Override
public 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方法逻辑:
@Override
public int compareTo(Student o) {
//return this.age - o.age;
return o.age - this.age;
}
PriorityQueue还有其它构造方法;比如传比较器的构造方法:PriorityQueue queue = new PriorityQueue<>(Comparator comparator);
这个构造方法创建一个空的优先级队列,并使用指定的比较器来确定元素的排序顺序。(这个使用比较器的指定顺序是比较优先的;还有一个构造方法是通过集合类和比较器构建优先级队列;那么不会用默认的比较方法;而是比较器的)
class IntCmp implements Comparator<Integer> {
@Override
public 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>() {
@Override
public int compare(Student o1, Student o2) {
return o2.age - o1.age;
}
});
最大或者最小的前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;复杂度这方面确实不是很行。能否优化一下呢?
逻辑: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>() {
@Override
public 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] 18
int val = minHeap.peek(); //27
if(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);没有浪费,创建额外的空间