堆 和 优先级队列

news2024/11/25 5:00:11

目录

一、堆

二、优先级队列 

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;
    }

    @Override
    public 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>{
    @Override
    public 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>() {
            @Override
            public 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>() {
            @Override
            public 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.coloradmin.cn/o/1583006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WordPress 图片压缩插件:Compress JPEG PNG images 使用方法

插件介绍 Compress JPEG & PNG images是一款非常好用的图片压缩插件:&#xff0c;非常值得大家安装使用&#xff1b;特别是图片类型网站。其实我们很多服务器磁盘空间是不在乎多那么几十 MB 大小的&#xff0c;但是压缩了图片能提升网站速度&#xff0c;节省宽带&#xff…

Linux网络名称空间之独立网络资源管理

Linux网络名称空间是一种强大的虚拟化技术&#x1f6e0;️&#xff0c;它允许用户创建隔离的网络环境&#x1f310;&#xff0c;每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用&#x1f4e6;和网络安全&#x1f512;等领域至关重要。本文将详细介绍在L…

python如何写入csv

在使用python对文件操作的过程中&#xff0c;你肯定碰到过对csv文件的操作&#xff0c;下面就python对csv文件的操作进行详述。 CSV&#xff08;Comma-Separated Values&#xff09;逗号分隔符&#xff0c;也就是每条记录中的值与值之间是用分号分隔的。 打开CSV文件并写入一…

第36篇:分频器<三>

Q&#xff1a;这一期我们介绍以计数器方式实现四分频的概念原理。 A&#xff1a;计数器分频有分频系数和占空比这两个参数。待分频时钟的频率为f1&#xff0c;分频后时钟的频率为f2&#xff0c;分频系数为Nf1/f2。 计数器分频电路通过对时钟信号计数来实现分频。根据分频系数可…

医院管理系统!(免费领取源码)

今天给大家分享一套基于SpringbootVue的医院管理系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文中自取) 系统运行图&#xff08;设计报告和接口文档&#xff09; 1、后台管理页面 2、排班管理页面 3、设计报告包含接口文档 源码免费领取方式 后台私信…

基于 SMM 汽车交易系统(源码+配套文档)

摘要 电子商务的兴起不仅仅是带来了更多的就业行业。同样也给我们的生活带来了丰富多彩的变化。多姿多彩的世界带来了美好的生活&#xff0c;行业的发展也是形形色色的离不开技术的发展。作为时代进步的发展方面&#xff0c;信息技术至始至终都是成就行业发展的重要秘密。不论…

如何彻底删除node和npm

如何彻底删除node和npm 前言&#xff1a; 最近做个项目把本地的node更新了&#xff0c;之前是v10.14.2更新至v16.14.0 &#xff0c;想着把之前的项目起来下&#xff0c;执行npm install 结果启动不了&#xff0c;一直报npm版本不匹配需要更新本地库异常… 找了几天发现是npm 和…

【优选算法专栏】专题十三:队列+宽搜(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

java常用API_正则表达式_在一段文本中查找满足要求的内容(爬虫)——练习及代码演示

练习一&#xff1a; 根据下面这段文本&#xff0c;爬取所有的JavaXX Java自从95年问世以来&#xff0c;经历了很多版本&#xff0c;目前企业中用的最多的是Java8和Java11&#xff0c;因为这两个是长期支持版本&#xff0c;下一个长期支持版本是Java17&#xff0c;相信在未来不久…

基于微信小程序的苏州博物馆文创产品售卖系统

前言 基于小程序的苏州博物馆文创产品售卖系统的设计与实现能够通过互联网得到广泛的、全面的宣传&#xff0c;让尽可能多的用户了解和熟知基于小程序的苏州博物馆文创产品售卖系统的设计与实现的便捷高效&#xff0c;不仅为群众提供了服务&#xff0c;而且也推广了自己&#…

【C++11】initializer_list | 右值引用 | 完美转发

一切皆可列表{ }初始化 在C98,允许花括号{ } 对数组、结构体类型初始化。 class Data { public:Data(int y, int m, int d):_y(y), _m(m), _d(d){} private:int _y;int _m;int _d; };int arr[4]{0,1,2,3};//列表初始化 Data d1{2024,03,21};//列表初始化 C11允许通过{ } 初始化…

基于ADS的PDK---DemoKit的切比雪夫滤波器RF芯片设计

基于ADS的PDK—DemoKit的切比雪夫滤波器RF芯片设计 由于版权原因&#xff0c;很少有完整的ADS的PDK在网上流传的&#xff0c;网上CSDN里面一些台积电的PDK都是只能老版本ADS2008才能用&#xff0c;或者干脆是Cadence导出来的&#xff08;Cadence导出PDK到ADS参考教程&#xff…

PPT 操作

WPS 版式 PPT中&#xff0c;巧妙使用母版&#xff0c;可以提高效率。 双击母版&#xff0c;选择其中一个版式&#xff0c;插入装饰符号。 然后选择关闭。 这个时候&#xff0c;在该版式下的所有页面&#xff0c;就会出现新加入的符号。不在该版式下的页面&#xff0c;不会出现…

飞桨Ai(一)基于训练后的模型进行信息提取

基准 本博客基于如下视频&#xff1a; 发票抬头信息抽取之环境搭建 - 基于飞浆开源项目发票抬头信息抽取之数据标准模型训练 - 基于飞浆开源项目 步骤 1、准备工作 下载python&#xff1a;【Python】Windows&#xff1a;Python 3.9.2 下载和安装&#xff08;建议3.9&#…

QDateTimeEdit设置按钮宽度无效

在对QDateTimeEdit组件的小按钮用qss样式加图标的时候&#xff0c;发现设置的宽度无效&#xff0c;原因是spacing属性必须设置才行。

【MATLAB源码-第29期】基于matlab的MIMO,MISO,SIMO,SISO瑞利rayleigh信道容量对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. SISO&#xff08;单输入单输出&#xff09;&#xff1a; - SISO 是指在通信系统中&#xff0c;只有一个天线用于传输信号&#xff0c;也只有一个天线用于接收信号的情况。这是最简单的通信方式。 2. SIMO&#xff08;单…

回归预测 | MATLAB实现BO-GRNN贝叶斯优化广义回归神经网络多输入单输出预测

回归预测 | MATLAB实现BO-GRNN贝叶斯优化广义回归神经网络多输入单输出预测 目录 回归预测 | MATLAB实现BO-GRNN贝叶斯优化广义回归神经网络多输入单输出预测预测效果基本介绍程序设计参考资料预测效果 基本介绍

C++奇迹之旅:探索类对象模型内存的存储猜想

文章目录 &#x1f4dd;前言&#x1f320; 类的实例化&#x1f309;类对象模型 &#x1f320; 如何计算类对象的大小&#x1f309;类对象的存储方式猜想&#x1f320;猜想一&#xff1a;对象中包含类的各个成员&#x1f309;猜想二&#xff1a;代码只保存一份&#xff0c;在对象…

韩顺平Java | C24 MySQL数据库(下)

※多表查询 笛卡尔集&#xff1a;查询两个表&#xff0c;默认无条件情况下&#xff0c;取出第一张表中的每一条记录和第二张表的每一条记录进行组合&#xff0c;返回row1*row2条记录数&#xff0c;包含两张表的所有列 内连接 # 写出正确的过滤条件&#xff1a;多表查询条件不…

【linux】yum 和 vim

yum 和 vim 1. Linux 软件包管理器 yum1.1 什么是软件包1.2 查看软件包1.3 如何安装软件1.4 如何卸载软件1.5 关于 rzsz 2. Linux编辑器-vim使用2.1 vim的基本概念2.2 vim的基本操作2.3 vim命令模式命令集2.4 vim底行模式命令集2.5 vim操作总结补充&#xff1a;vim下批量化注释…