LC215 数组中的第K个最大元素
class Solution {
static Comparator<Integer> cmp = new Comparator<Integer>(){
@Override
public int compare(Integer i1, Integer i2){
return i1 - i2;//升序排列
// return i2 - i1;//降序
}
};
public static int findKthLargest(int[] nums, int k) {
int len = nums.length;
PriorityQueue<Integer> minTop = new PriorityQueue<>(k,cmp);
for (int i = 0; i < k; i++){
minTop.add(nums[i]);
//add 添加元素
}
for (int i = k; i < len; i++){
Integer topEle = minTop.peek(); // 返回队头元素(不删除),失败时前者
//peek取出队头元素,不删除
// 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去(把最小的元素排出去)
if (nums[i] > topEle){
minTop.poll(); // 获取队头元素并删除,并返回,失败时前者抛出null,
minTop.offer(nums[i]); // 在队尾插入元素,插入失败时抛出false,并
}
}
return minTop.peek();//(k个里面最小的,就是第k大的)
}
}
堆
定义:
最大堆是一种完全二叉树,其中每个节点的值都大于等于其子节点的值(也就是根节点是最大的),除根节点外每个节点都满足父节点的值大于等于其子节点的堆属性。(大根堆)
最小堆也是一种完全二叉树,其中每个节点的值都小于等于其子节点的值(也就是根节点是最小),除根节点外每个节点都满足父节点的值小于等于其子节点的堆属性。
应用:
堆排序是一种利用堆的数据结构进行排序的算法,nlogn
优先队列:常用于资源调度,任务调度
中位数维护:使用两个堆来维护数据流中的中位数,一个堆最大堆存数据流的前半部分,一个最小堆存数据流后半部分。
最大堆(优先队列)的删除
最大堆的操作先从简单的来,最大堆的设计就是为了方便将最大元素或最小元素以最小的代价给出。
从上面的树中,删除一个最大元素将使得根结点被弹出。需要对根结点经行补全,此时要寻找最大堆中剩余节点中的最大元素补到根结点的位置,也就是数组中下标为1的位置。
很显然,下面的元素中,最大元素必然是根节点的左右子树中较大的那一个。
我们将根节点补充为其较大的那一个儿子。而这个被拿去补充根节点的元素所占的位置仍然需要其较大的子结点去补充,这样不断向下,直到被拿去补充的元素不再拥有左右子树,退出循环。
最大堆(优先队列)的插入
最大的的插入与删除类似于反向操作。
我们首先将要插入的数据放在最大堆的尾部,然后比较其父亲子树与该结点的参数大小,若插入的参数大于其父亲结点的参数,则二者做交换。在不断向上交换的过程中,循环什么时候结束呢?
没错,当比较的元素N/2为0,我们先前定的Flag时,则退出循环,若是最大堆可以将Flag定位无穷大,反之可以定为0或无穷小。
优先队列(Java的PriorityQueue)
PriorityQueue实现了Queue接口,可以存放基本数据类型的包装类或自定义的类(前提是都可排序,必须实现Comparable接口,并根据需要重写CompareTo方法)。
对于基本数据类型的包装类,优先队列中元素默认排列顺序是升序排列。
对于自定义的类,需要定义比较器。
请注意,此实现不同步。 如果任何线程修改队列 ,多线程不应同时访问PriorityQueue实例。 而是使用线程安全的 PriorityBlockingQueue类。
Comparator<Object> cmp = new Comparator<Object>() {
public int compare(Object o1, Object o2) {
//升序
return o1-o2;
//降序
return o2-o1;
}
};
可以使用以下方法来操作优先队列
insert(key):向优先队列中插入一个元素,时间复杂度为O(logN)。
deleteMin():删除并返回优先队列中优先级最高的元素,时间复杂度为O(logN)。
getMin():返回优先队列中优先级最高的元素,但不进行删除,时间复杂度为O(1)。
add(元素)或offer(元素):向队列中添加元素。
remove()或poll():移除并返回队列中的第一个(最高优先级)元素。
peek():返回队列中的第一个(最高优先级)元素,但不进行移除。
isEmpty():检查队列是否为空。
size():返回队列中的元素个数。
默认情况下,优先队列中的元素按照自然顺序进行排序。但也可以通过传递一个自定义的Comparator对象来指定元素的比较规则。
关于PriorityQueue的使用要注意:
- 使用时必须导入 PriorityQueue 所在的包,即:
import java.util.PriorityQueue;
-
PriorityQueue 中放置的 元素必须要能够比较大小,不能插入无法比较大小的对象 ,否则会抛出 ClassCastException异常
-
不能插入null对象 ,否则会抛出 NullPointerException
-
没有容量限制,可以插入任意多个元素,其内部可以自动扩容
-
插入和删除元素的时间复杂度为O(logN)
-
PriorityQueue 底层使用了 堆数据结构
-
PriorityQueue 默认情况下是小堆 --- 即每次获取到的元素都是最小的元素
默认情况下,PriorityQueue队列是小堆(即队首是最小元素,升序),如果需要大堆需要用户提供比较器
// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
p.offer(4);
p.offer(3);
p.offer(2);
p.offer(1);
p.offer(5);
System.out.println(p.peek());
}
}
常用方法:
如果容量小于 64 时,是按照 oldCapacity 的 2 倍方式扩容的
如果容量大于等于 64 ,是按照 oldCapacity 的 1.5 倍方式扩容的
如果容量超过 MAX_ARRAY_SIZE ,按照 MAX_ARRAY_SIZE 来进行扩容
优先队列应用
任务调度:在多线程或分布式系统中,任务调度是一个重要的问题。优先队列可以根据任务的优先级来决定执行顺序,高优先级的任务会被首先执行。
搜索算法:在图搜索、最短路径、A*算法等问题中,优先队列可以用来存储待探索的节点,并按照启发式函数的值(估计到目标的距离)进行排序,以便选择下一个最有希望的节点进行探索。
带有时间限制的调度问题:在某些场景下,任务可能有截止时间。优先队列可以根据任务的截止时间来确定执行顺序,确保在截止时间前完成最高优先级的任务。
数据压缩:在哈夫曼编码等数据压缩算法中,优先队列可以用来存储字符及其出现频率,然后构建压缩树。
操作系统调度:操作系统中的进程调度也可以使用优先队列来实现,根据进程的优先级来决定调度顺序。
参考:chatGPT
【Java】PriorityQueue--优先级队列_java priorityqueue-CSDN博客
学习一波Java语言中的优先队列 PriorityQueue_java 优先队列-CSDN博客