目录
Java 堆相关知识点
1. 堆的初始化
2. 堆的相关函数
3. 堆的原理
4. 运用堆进行排序
5. 怎么用优先队列实现大顶堆:
示例代码
自定义比较器实现大顶堆
总结
刷题:
数组中的第K个最大元素
前K个高频元素
数据流的中位数
在 Java 中,堆是一种数据结构,通常用于实现优先队列。堆可以通过 PriorityQueue
类来初始化。PriorityQueue
是一个基于优先级的无界队列,底层实现是一个数组,它默认按照自然顺序进行排序,也可以通过自定义比较器进行排序。
示例代码:
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
// 初始化一个堆
PriorityQueue heap = new PriorityQueue<>();
// 添加元素到堆中
heap.add(3);
heap.add(1);
heap.add(2);
// 输出堆的内容
System.out.println(heap); // 输出: [1, 3, 2]
}
}
PriorityQueue
提供了一系列方法来操作堆:
add(E e)
:将指定元素插入堆中。
offer(E e)
:将指定元素插入堆中,与 add
类似,但不抛出异常。
remove()
:移除并返回堆顶元素(最小值)。
poll()
:移除并返回堆顶元素,与 remove
类似,但不抛出异常。
peek()
:返回但不移除堆顶元素。
size()
:返回堆中的元素数量。
clear()
:清空堆中的所有元素。
示例代码:
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
PriorityQueue heap = new PriorityQueue<>();
heap.add(3);
heap.add(1);
heap.add(2);
// 获取并移除堆顶元素
System.out.println(heap.poll()); // 输出: 1
System.out.println(heap.poll()); // 输出: 2
System.out.println(heap.poll()); // 输出: 3
// 检查堆是否为空
System.out.println(heap.isEmpty()); // 输出: true
}
}
堆是一种特殊的树形数据结构,通常是一个完全二叉树。堆分为两种类型:最大堆和最小堆。在最小堆中,父节点的值总是小于或等于子节点的值;在最大堆中,父节点的值总是大于或等于子节点的值。
PriorityQueue
默认实现的是最小堆。堆的底层实现通常使用数组,通过索引计算父节点和子节点的位置:
父节点索引:(index - 1) / 2
左子节点索引:2 * index + 1
右子节点索引:2 * index + 2
堆排序是一种利用堆数据结构进行排序的算法。堆排序的基本步骤如下:
构建堆:将数组构建成一个最大堆或最小堆。
排序:反复取出堆顶元素(最大值或最小值),并将剩余元素重新调整为堆。
示例代码:
java复制
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
int[] arr = {3, 1, 2, 5, 4};
// 使用最小堆进行排序
PriorityQueue heap = new PriorityQueue<>();
for (int num : arr) {
heap.add(num);
}
// 将元素按顺序取出,得到排序后的数组
for (int i = 0; i < arr.length; i++) {
arr[i] = heap.poll();
}
// 输出排序后的数组
for (int num : arr) {
System.out.print(num + " "); // 输出: 1 2 3 4 5
}
}
}
在 Java 中,PriorityQueue
类默认实现的是 小顶堆(最小堆)。这意味着堆顶元素(即队列的头部)是堆中最小的元素。
java复制
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
PriorityQueue heap = new PriorityQueue<>();
heap.add(3);
heap.add(1);
heap.add(2);
// 输出堆顶元素(最小值)
System.out.println(heap.peek()); // 输出: 1
// 移除堆顶元素并输出
System.out.println(heap.poll()); // 输出: 1
System.out.println(heap.peek()); // 输出: 2
}
}
如果需要实现大顶堆(最大堆),可以通过自定义比较器(Comparator
)来实现:
java复制
import java.util.PriorityQueue;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
// 使用自定义比较器实现大顶堆
PriorityQueue heap = new PriorityQueue<>(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 降序排列
}
});
heap.add(3);
heap.add(1);
heap.add(2);
// 输出堆顶元素(最大值)
System.out.println(heap.peek()); // 输出: 3
// 移除堆顶元素并输出
System.out.println(heap.poll()); // 输出: 3
System.out.println(heap.peek()); // 输出: 2
}
}
堆的初始化:使用 PriorityQueue
类来初始化堆,PriorityQueue默认小顶堆。
堆的相关函数:add
、offer
、remove
、poll
、peek
等方法用于操作堆。
堆的原理:堆是一种完全二叉树,分为最大堆和最小堆,底层实现通常使用数组。
运用堆进行排序:通过构建堆和反复取出堆顶元素实现排序。
215. 数组中的第K个最大元素
思路:将数组中的所有元素添加到大顶堆中,然后第K大的元素就是将前K-1个元素从堆中poll出来,这个时候堆顶元素就是result。
class Solution {
public int findKthLargest(int[] nums, int k) {
int result;
PriorityQueue pq = new PriorityQueue<>(new Comparator(){
@Override
public int compare(Integer a,Integer b){
return b-a;
}
});
for(int num : nums){
pq.add(num);
}
for(int i = k-1 ;i >0;i--){
pq.poll();
}
result = pq.peek();
return result;
}
}
优化思路:继续使用大顶堆:不需要重写compare函数;Lambda 表达式的箭头 ->
后面的内容表示 Lambda 表达式的主体,(a,b)是给接口的参数。
PriorityQueue pq = new PriorityQueue<>((a, b) -> b - a);
使用小顶堆:维护堆的大小只需要K就行,堆顶刚好是第K大的数。
class Solution {
public int findKthLargest(int[] nums, int k) {
int result;
PriorityQueue pq = new PriorityQueue<>();
for(int num : nums){
pq.add(num);
if(pq.size()>k) pq.poll();
}
result = pq.peek();
return result;
}
}
347. 前 K 个高频元素
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap map = new HashMap<>();
for(int num: nums){
if(map.containsKey(num)) map.put(num,map.get(num)+1);
else{
map.put(num,1);
}
}
PriorityQueue pq = new PriorityQueue<>(new Comparator(){
@Override
public int compare(Integer a,Integer b)
{
return map.get(a)-map.get(b);
}
});
for(Integer key :map.keySet()){
if(pq.size()map.get(pq.peek())){
pq.poll();
pq.add(key);
}
}
int[] result = new int[k];
for(int i = 0;i
295. 数据流的中位数
思路:创建两个堆,一个大顶堆,一个小顶堆,大顶堆中根节点就是中位数,所以只有比根节点小的数能进去(这个时候很有可能第一个数很大,数全在大顶堆中,这个时候就要判断两个堆里面的个数,他俩最多大顶堆比小顶堆多一个数),小顶堆存放的是比中位数小的。
big = new PriorityQueue<>((a,b)->b-a);这里调用了comparator接口,如果 compare(a, b)
返回正数,表示 a
应该排在 b
之后。当b-a>0,说明b比a大,a在b后面,是大顶堆。
class MedianFinder {
PriorityQueue big;
PriorityQueue small;
public MedianFinder() {
big = new PriorityQueue<>((a,b)->b-a);
small = new PriorityQueue<>();
}
public void addNum(int num) {
if(big.isEmpty()||numsmall.size()+1) small.add(big.poll());
}
else{
small.add(num);
if(big.size()