目录
一、优先级队列
优先级队列:底层基于堆的队列,可以按照元素间优先级的大小动态顺序出队。
基于最大堆的优先级队列:
import queue.Queue;
/**
* 基于最大堆的优先级队列实现,值越大优先级越高
* 队首元素就是优先级最大的元素
*/
public class PriorityQueue implements Queue<Integer> {
private MaxHeap heap;
public PriorityQueue(){
heap=new MaxHeap();
}
@Override
public void offer(Integer val) {
heap.addNode(val);
}
@Override
public Integer poll() {
return heap.extractMax();
}
@Override
public Integer peek() {
return heap.peekMax();
}
@Override
public boolean isEmpty() {
return heap.isEmpty();
}
}
但是JDK的优先级队列默认是最小堆,要将最小堆改为最大堆就需要知道元素间是怎么比较大小的。
元素间大小比较:
1、实现Comparable接口:
升序排列
降序排列
2、传入java.util.Comparator -> 比较器 :
使用Comparator将JDK的最小堆改为最大堆:
二、TopK问题
TopK问题:从一堆数据中选出前**多少个数——应用堆来解决问题,取大用小,取小用大。
2.1 面试题 17.14.最小K个数
题目描述如下:
解题思路:要找出数组中最小的k个数,就要构造一个有k个元素的最大堆,最大堆的堆顶元素值最大,扫描数组入队,直到队列中元素为k个,然后比较堆顶元素和扫描的元素,如果堆顶元素小于扫描元素,继续扫描,如果堆顶元素大于扫描元素 ,就将堆顶元素出队,扫描元素入队,不断“打擂”,将更小的元素换到堆中。
代码如下:
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
/**
* 最小K个数
*/
public class Offer17_14_SmallestK {
public int[] smallestK(int[] arr, int k) {
if (arr.length==0||k==0){
return new int[0];
}
//JDK默认是最小堆,需要使用比较器来让它变成最大堆
Queue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i:arr){
//当堆中元素个数小于k时,入队
if (queue.size()<k){
queue.offer(i);
}else{
//否则比较队首元素和i的大小,此时i更大则继续比较,
// i小于队首元素时,将队首出队,然后将i入队
int x=queue.peek();
if (i>x){
continue;
}else{
queue.poll();
queue.offer(i);
}
}
}
int[] ret=new int[k];
for (int i = 0; i < ret.length; i++) {
ret[i]=queue.poll();
}
return ret;
}
}
2.2 Num347.前k个高频元素
题目描述如下:
解题思路:本题要求返回出现频次前k高的元素,所以需要比较元素出现的次数,首先将数组中出现的元素以及它得出现次数存到Map中,然后扫描Map集合,将最小堆依次出队即得到了答案,定义一个类,来存放数组中的每个元素以及它出现的次数,会大大提高解题效率 。
代码如下:
import java.util.*;
/**
* 前K个高频元素
* 示例 1:
* 输入: nums = [1,1,1,2,2,3], k = 2
* 输出: [1,2]
*/
public class Num347_TopKFrequent {
private class Freq{
int key;
int times;
public Freq(int key, int times) {
this.key = key;
this.times = times;
}
}
public int[] topKFrequent(int[] nums, int k) {
int[] ret=new int[k];
//1.遍历数组,将数组中每个元素和它出现的次数存进map集合中
Map<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
//此时map中还没有存入nums[i]
if (!map.containsKey(nums[i])){
map.put(nums[i],1);
}else{
//此时nums[i]已经存过了
map.put(nums[i],map.get(nums[i])+1);
}
}
Queue<Freq> queue=new PriorityQueue<>(new Comparator<Freq>() {
@Override
public int compare(Freq o1, Freq o2) {
return o1.times-o2.times;
}
});
//2.遍历map,将当前出现频次最高的前k个数入队
for (Map.Entry<Integer,Integer> entry:map.entrySet()){
if (queue.size()<k){
queue.offer(new Freq(entry.getKey(),entry.getValue()));
}else{
Freq freq=queue.peek();
if (freq.times> entry.getValue()){
continue;
}else{
queue.poll();
queue.offer(new Freq(entry.getKey(),entry.getValue()));
}
}
}
for (int i = 0; i < k; i++) {
ret[i]=queue.poll().key;
}
return ret;
}
}
2.3 Num373.查找和最小的k对数
题目描述如下:
特殊情况:当k大于数组长度时
解题思路:使用最大堆来解决问题,定义一个类Pair,将两个数组中的元素分别存进去,然后遍历两个数组,比较元素和谁大谁小,最后将最大堆出队即可。
代码如下:
import java.util.*;
/**
* 查找和最小的K对数字
*/
public class Num373_KSmallestPairs {
private class Pair implements Comparable<Pair>{
int u;
int v;
public Pair(int u, int v) {
this.u = u;
this.v = v;
}
//实现Comparable接口将最小堆改造为最大堆
@Override
public int compareTo(Pair o) {
return (o.u+o.v)-(this.u+this.v);
}
}
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
Queue<Pair> queue=new PriorityQueue<>();
//遍历两个数组,u来自第一个数组,v来自第二个数组
//循环终止条件Math.min(nums1.length,k),其中k可能大于数组长度
for (int i = 0; i < Math.min(nums1.length,k); i++) {
for (int j = 0; j < Math.min(nums2.length,k); j++) {
if (queue.size()<k){
queue.offer(new Pair(nums1[i],nums2[j]));
}else{
int sum=nums1[i]+nums2[j];
Pair peek=queue.peek();
if ((peek.u+peek.v)<sum){
continue;
}else{
queue.poll();
queue.offer(new Pair(nums1[i],nums2[j]));
}
}
}
}
List<List<Integer>> ret=new ArrayList<>();
//此时i不仅要小于k,队列还不能为空
for (int i = 0; i < k&&(!queue.isEmpty()); i++) {
List<Integer> list=new ArrayList<>();
Pair pair=queue.poll();
list.add(pair.u);
list.add(pair.v);
ret.add(list);
}
return ret;
}
}
三、堆排序
堆排序:
- 将数组调整为最大堆;
- 然后交换arr[0]和最后一个元素,此时最后一个元素就是数组中的最大值;
- 从0到倒数第二元素开始进行siftDown操作;
代码如下:
/**
* 堆排
* @param arr
*/
public static void heapSort(int[] arr){
//先将arr调整为最大堆,从最后一个非叶子节点开始进行siftDown操作
for (int i = (arr.length-1-1)/2; i >=0 ; i--) {
siftDown(arr,i,arr.length);
}
//此时arr已经被调整为最大堆
for (int i = arr.length-1; i >0 ; i--) {
//交换arr[0]和最后一个元素
swap(arr,0,i);
//然后从0到倒数第二个元素开始进行下沉操作
siftDown(arr,0,i);
}
System.out.println(Arrays.toString(arr));
}
/**
*元素下沉操作
* @param arr
* @param i
* @param length
*/
private static void siftDown(int[] arr, int i, int length) {
//当前节点存在左子树
while(2*i+1<length){
//此时j为左子树节点
int j=2*i+1;
//如果当前节点存在右子树并且右子树的值大于左子树的值
if ((j+1)<length&&arr[j+1]>arr[j]){
//此时j为右子树节点
j=j+1;
}
//比较当前节点值与其左右子树值的大小
if (arr[i]>arr[j]){
break;
}else{
swap(arr,i,j);
i=j;
}
}
}
//交换
private static void swap(int[] arr, int i, int j) {
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
public static void main(String[] args) {
int[] arr={3,6,2,5,9,12,90,24,1};
heapSort(arr);
}