堆 和 优先级队列

news/2025/1/16 14:08:10/

目录

一、堆

二、优先级队列 

1、初识优先级队列 

2、实现一个优先级队列 

3、PriorityQueue 

(1)实现了Comparable接口,重写了compareTo方法

(2)实现了Comparator接口,重写了compare方法

4、 PriorityQueue 的应用

Top-k问题:使用 PriorityQueue 来做


一、堆

  1. 堆 是一种数据结构,是在完全二叉树的基础上进行了一些调整。
  2. 堆 分为大根堆 和 小根堆,根结点比左右结点都大的堆叫做 大根堆,根结点比左右结点都小的堆叫小根堆。
  3. 堆 的存储结构(物理结构):顺序存储,即会有一个数组,按照层序的方式顺序存储
  4. 若 父结点下标为 i,则左孩子下标为 2*i+1,右孩子下标为2*i+2;若 子结点下标为 i,则父结点下标为 (i-1)/2

二、优先级队列 

1、初识优先级队列 

优先级队列(PriorityQueue)底层是小根堆。

2、实现一个优先级队列 

采用顺序存储的结构,使用数组来实现。

建堆的两种方法:建堆的时间复杂度是O(n) 

1、先给elem数组赋值,创建一棵完全二叉树。然后从最后一棵子树的根结点开始调整,将每棵子树都调整成小根堆。调整的方案是向下调整(shiftDown)

shiftDown的时间复杂度:O(log n)

2、从无到有,不先创建完全二叉树,而是每放一个数据,都需要调整成小根堆。调整的方案是向上调整(shiftUp)

shiftUp的时间复杂度: O(log n)

对于优先级队列来说,入队和出队的时间复杂度 O(log n)  

import java.util.Arrays;//底层是小根堆
public class MyPriorityQueue {public int[] elem;public int size;//数组的有效长度public static final int DEFAULT_SIZE = 10;public MyPriorityQueue(){this.elem = new int[10];}/*** 2、建堆第二种方法:从无到有,不先创建完全二叉树,而是每放一个数据,都需要调整成小根堆。*/public void createHeap2(int data){if(isEmpty()){elem[0] = data;size++;return;}if(isFull()){elem = Arrays.copyOf(elem,elem.length*2);}elem[size] = data;size++;shiftUp(size-1);}/*** 1、建堆第一种方法:先给elem数组赋值,创建一棵完全二叉树。然后从最后一棵子树的根结点开始调整,将每棵子树都调整成小根堆。*///给数组赋值,创建了一棵完全二叉树public void createTree(int[] arr){for (int i = 0; i < arr.length; i++) {this.elem[i] = arr[i];this.size++;}}//将完全二叉树调整成小根堆public void createHeap(){//从最后一个根结点开始往前调整int parent = ((size-1)-1)/2;for (int i = parent; i >= 0 ; i--) {shiftDown(i);}}//将根为parent的树调整为小根堆public void shiftDown(int parent){int child = 2*parent+1;while(child < size){//child+1 < size 保证有右树if(child+1 < size && elem[child+1] < elem[child]){child++;}//走到这,child 是左右子树最小值的下标if(elem[child] < elem[parent]){swap(child,parent);parent = child;child = 2*parent+1;}else{break;}}}public void swap(int x,int y){int tmp = elem[x];elem[x] = elem[y];elem[y] = tmp;}//入队:时间复杂度 O(log n)public void offer(int data){if(isFull()){elem = Arrays.copyOf(elem,elem.length*2);}elem[size] = data;size++;shiftUp(size-1);}public boolean isFull(){return size == this.elem.length;}public void shiftUp(int child){int parent = (child-1)/2;while(child > 0){if(elem[child] < elem[parent]){swap(child,parent);child = parent;parent = (child-1)/2;}else{break;}}}//出队:时间复杂度 O(log n)public int poll(){if(isEmpty()){return -1;}int delete = elem[0];swap(0,size-1);size--;//这样就只需要将parent=0的这棵树调整为小根堆就行了shiftDown(0);return delete;}public boolean isEmpty(){return size == 0;}//获取队顶元素但不删除public int peek(){if(isEmpty()){return -1;}return elem[0];}}

3、PriorityQueue 

  1. PriorityQueue 底层是小根堆。
  2. PriorityQueue 没有传数组容量时,默认的初始容量是11;如果传容量,不能<1,否则
    会抛 IllegalArgumentException 异常
  3. PriorityQueue 放入的数据必须得能比较大小,即插入的数据要么实现了Comparable<T>接口,要么 实现了Comparator<T>接口,否则会抛出 ClassCastException异常
  4. PriorityQueue 放入的数据如果实现了比较器,要把比较器传过去,优先使用比较器来比较
  5. PriorityQueue 不能插入null对象,否则会抛出NullPointerException异常
  6. PriorityQueue 底层会自动扩容,容量<64时会2倍扩容,容量>=64时会1.5倍扩容

(1)实现了Comparable<T>接口,重写了compareTo方法

import java.util.PriorityQueue;
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;}
}
public class Test {public static void main(String[] args) {PriorityQueue<Student> priorityQueue = new PriorityQueue<>();priorityQueue.offer(new Student(20));priorityQueue.offer(new Student(10));priorityQueue.offer(new Student(30));System.out.println();}
}

 

我们发现,数据确实有序了,而且是小根堆的形式。当然,我们也可以把它变成大根堆。

我们将  return this.age - o.age;改成 return o.age - this.age; 后,就变成了大根堆的形式。

(2)实现了Comparator<T>接口,重写了compare方法

那么,Integer怎么变成大根堆形式呢,Integer的compareTo方法是在源码里写好的,我们改不了源码。于是就用到了比较器,传个比较器过去,在比较器里重写compare方法,就可以改了。

class IntCmp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);}
}
public class Test {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());priorityQueue.offer(20);priorityQueue.offer(10);priorityQueue.offer(30);System.out.println();}
}

 

我们将  return o1.compareTo(o2);改成return o2.compareTo(o1);后,就变成了大根堆的形式。

4、 PriorityQueue 的应用

Top-k问题:使用 PriorityQueue 来做

求最的前k个元素或第k大的元素,就 将前 k 个数建立成小根堆;

求最的前k个元素或第k小的元素,就 将前 k 个数建立成大根堆。

时间复杂度:n*log k(堆的大小是k,数组中元素的个数是n)

(1)数据集合中最大或最小的前k个元素(一般情况下,数据量都会非常大。)

如:找出数组中最小的k个数,以任意顺序返回这k个数均可。 

解题思路: 

  1. 将前 k 个数建立成大根堆
  2. 从第 k+1 个数据开始,每次都和堆顶元素比较,如果比堆顶元素小,就弹出堆顶元素,把这个元素放进堆
  3. 那么最后,堆中的这k个元素就是数组中最小的k个数

为什么找 最小的k个数 要建大根堆呢?

因为,大根堆的堆顶元素是最大的,

若后面的数据比它大,说明肯定不属于 最小的k个数;若后面的数据比它小,说明它肯定不属于 最小的k个数,那么就把它弹出,把这个数据放进去。以此类推。最后这个k大小的堆中就是最小的k个数了。

 //找出数组中最小的k个数public static int[] topK(int[] arr,int k){if(arr == null || k == 0) {return new int[0];}PriorityQueue<Integer> min = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int i = 0; i < k; i++) {min.offer(arr[i]);}//到这里,建了一个大小为3的 大根堆for (int i = k; i < arr.length; i++) {int peek = min.peek();if(arr[i] < peek){min.poll();min.offer(arr[i]);}}//走到这,min 里就是数组中最小的k个数int[] tmp = new int[k];for (int i = 0; i < k; i++) {tmp[i] = min.poll();}return tmp;
}

(2)第k小/第k大的元素

如:找出数组中第k小的元素

解题思路:

  1. 将前 k 个数建立成大根堆
  2. 从第 k+1 个数据开始,每次都和堆顶元素比较,如果比堆顶元素小,就弹出堆顶元素,把这个元素放进堆
  3. 那么最后,堆顶元素就是第k小的元素
//找出数组中第k小的数public static int least(int[] arr,int k){if(arr == null || k == 0) {return -1;}PriorityQueue<Integer> min = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int i = 0; i < k; i++) {min.offer(arr[i]);}//到这里,建了一个大小为3的 大根堆for (int i = k; i < arr.length; i++) {int peek = min.peek();if(arr[i] < peek){min.poll();min.offer(arr[i]);}}//走到这,min 里就是数组中最小的k个数return min.peek();
}

http://www.ppmy.cn/news/1413361.html

相关文章

AWD靶机实战第二天:使用python脚本获取flag

上一部分我们通过读源码和利用源码审计的工具找到了这个漏洞&#xff0c;但是在比赛的时候有很多靶机&#xff0c;我们去一个个的注册&#xff0c;一个个的登录然后输入很浪费时间&#xff0c;所以我选择写一个python脚本来实现自动获得flag以及自动提交flag. 首先我们将这次的…

心跳机制原理学习

心跳机制 应用场景&#xff1a; 在长连接下&#xff0c;有可能很长一段时间都没有数据往来。理论上说&#xff0c;这个连接是一直保持连接的&#xff0c;但是实际情况中&#xff0c;如果中间节点出现什么故障是难以知道的。更要命的是&#xff0c;有的节点&#xff08;防火墙…

Vue - 4( 8000 字 Vue 入门级教程)

一&#xff1a; Vue 初阶 1.1 关于不同版本的 Vue Vue.js 有不同版本&#xff0c;如 vue.js 与 vue.runtime.xxx.js&#xff0c;这些版本主要针对不同的使用场景和需求进行了优化&#xff0c;区别主要体现在以下几个方面&#xff1a; 完整版 vs 运行时版&#xff1a; vue.js&…

Java RESTful API开发:搭建基于Spring Boot的轻量级API服务

引言&#xff1a; 在当今互联网时代&#xff0c;API&#xff08;Application Programming Interface&#xff09;已经成为了各种软件系统之间交互的重要方式。而REST&#xff08;Representational State Transfer&#xff09;则是一种设计风格&#xff0c;用于构建分布式系统中…

c++作业day4

头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimer> #include <QTime> #include <QTextToSpeech> #include <QMessageBox> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass…

备战蓝桥杯Day40 - 第11届python组真题 - C跑步锻炼

一、题目描述 二、思路 1、使用datetime库中的方法可以很好的解决这个问题。 2、定义起始时间和结束时间&#xff0c;判断是否是周一或者是1号&#xff0c;结果res加上相应的里程数。 3、最后输出 res 即为本题答案。 三、代码实现 import datetimestart datetime.date(2…

【Java+Springboot】------ 通过JDBC+GetMapping方法进行数据select查询、多种方式传参、最简单的基本示例!

一、JDBC如何使用、PostGresql数据库 1、在pom.xml 先引用jdbc组件。 <!--jdbc--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency> 2、在pom.xml 再引用p…

国内ChatGPT大数据模型

在中国&#xff0c;随着人工智能技术的迅猛发展&#xff0c;多个科技公司和研究机构已经开发出了与OpenAI的ChatGPT类似的大型语言模型。这些模型通常基于深度学习技术&#xff0c;尤其是Transformer架构&#xff0c;它们在大量的文本数据上进行训练&#xff0c;以理解和生成自…