优先级队列PriorityQueue(堆)

news2024/11/22 7:17:44

1. 优先级队列

        队列是一种先进先出的数据结构,而如果我们操作的数据带有优先级,我们出队的时候就会先出队优先级最高的元素.比如打游戏的时候有人给你打电话,操作系统看来有俩个进程,优先会处理打电话.

        主要功能

        1> 返回最高优先级对象

        2> 添加新的对象

2. 堆的概念

        2.1 认识堆

优先级队列是堆的一种.我们先来看看底层的集合,我们可以从图中看出,PriorityQueue实现了Queue接口,优先级队列就是一种完全二叉树.PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。

      

        2.2 堆的分类

        堆分为大根堆和小根堆

        1> 小根堆

       根结点一定小于孩子结点->小根堆

        2> 大根堆
       根结点一定大于孩子结点->大根堆                

        2.3 堆的存储方式

        其实从开始我们强调堆是一种完全二叉树,并且从上面来看,我们的堆是用数组来存储的(数组里面的存储顺序是层序遍历),那么为什么要这样呢?为什么完全二叉树要用数组来存储呢?数组可以存储非完全二叉树吗?

        如图可以看出,完全二叉树存到数组里面是不会浪费空间,而非完全二叉树会有空间的浪费,

因此有以下结论:

                非完全二叉树->用链式结构
                完全二叉树->用数组存储

        2.4 重要的性质

        已知双亲求孩子: 左孩子: 2 * i +1,右孩子:2 * i +2

        已知孩子求双亲: 双亲: ( i - 1 ) / 2

        

3. 模拟实现大根堆

        3.1 前置变量介绍

                我们使用数组来实现堆的创建,数组的顺序就是我们二叉树层序遍历的顺序,首先我们定义一个数组elem,然后我们用usedSize来记录当前堆中有效数据的个数.我们再提供一个构造方法,初始化数组容量为10,然后我们提供一个initElem方法,用来接受用户给定的数组,把用户给定的数组逐个赋值到elem里面去.

        3.2  数组调整成大根堆

        主要的调整过程:

        1. 从最后一棵子树开始调整.

        2. 找到左右孩子的最大值和根结点进行比较,如果比根结点大,就进行交换

        3. 主方法: 父节点下标 = (usedSize - 1 - 1) / 2; siftDown方法: 左孩子下标 = parent*2+1 

        4. 一直调整到0下标的这棵树为止.

就差不多和我下面第一张图的过程差不多,反正每次主方法调用siftDown都要从当前根结点一直向下进行调整.

时间复杂度: O(n)

具体的推导方法:

如果我们采用向上调整的方式则会复杂一些,因为我们还要调整最后一层: O(n*logn)

        3.3 堆的插入

             在堆里面我们通过向上调整进行元素的插入,先从主方法把我们的元素放在我们二叉树的最后部分上,此时还需要进行堆是否是满的判断,如果是满的就扩容,然后我们进入siftUp方法,我们把usedSize传进去,然后依次让插入元素和每个父节点进行比较,如果大的话就进行交换,然后进入下一次循环,如果小于就直接退出循环了,因为本身就是一个大根堆,如果比最后的父节点还小,那么前面的父节点就更不可能比它大了.

        3.4 堆的删除

主要步骤:

1. 我们把数组0下标的值和最后一个下标的值交换

2. 对0下标进行向下查找.

        3.5 小总结

        数组调整成大根堆 : 对每个父节点进行向下调整.

        堆的元素插入: 插入到最后,然后和每个父节点进行比较,进行向上调整

        堆的元素删除: 0和最后下标进行交换,然后我们对0下标进行向下调整.

        3.6 具体代码

package 优先级队列_堆;

import ArrayList和顺序表.mylist.MyArrayListEmpty;

import java.util.Arrays;

public class MyHeap {
    private int[] elem;
    public int usedSize;//记录当前堆中有效数据的个数
    public MyHeap() {
        this.elem = new int[10];
    }
    //初始化数组
    public void initElem(int[] array) {
        for (int i = 0; i < array.length ; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }
    //在这里把我们的数组变成一个大根堆
    public void createHeap() {
        //usedSzie - 1 表示的是我们孩子结点的下标,已知孩子求父亲,那么就再-1,然后/2
        for (int parent = (usedSize - 1 - 1 )/2; parent >= 0   ; parent--) {//确定每棵子树parent的下标
            //每棵子树向下调整
            siftDown(parent,usedSize);//parent确定每颗子树的根结点,usedSize确定每颗子树的结束位置
        }
    }
    //向下调整
    //时间复杂度O(n)
    private void siftDown(int parent, int len) {
        //首先我们知道了父节点的下标,我们根据公式求出它左孩子的下标
        int child = parent * 2 + 1;
        //我们来找最大值
        while (child < len) {
            //先找到左右孩子的最大值下标
            if(child + 1 < len && elem[child] < elem[child + 1]) {
                child = child + 1;//child记录的是最大值的下标
            }
            //再比较和根结点的大小
            if(elem[child] > elem[parent]) {
                //交换值
                int temp = elem[parent];
                elem[parent] = elem[child];
                elem[child] = temp;
                //跟新child的下标
                parent = child;
                child = parent * 2 + 1;
            }else {
                //本身是最大值,则不调整了,直接退出循环
                break;
            }
        }

    }
    //TODO 堆的插入和删除
    //向上调整
    //直接插入到最后,然后和根进行大小的比较
    public void push(int val) {
        //判断是否是满的
        if(isFull()){
            //是满的就扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);
        usedSize++;

    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    public void siftUp(int child) {
        //已知孩子下标求父结点下标
        int parent = (child - 1) / 2;
        //比较父亲和插入结点的大小
        while (child > 0)
        if(elem[parent] < elem[child]) {
            //如果小于就交换值
            swap(parent,child);
            //更新
            child = parent;
            parent = (child - 1)/2;
        }else {
            break;//因为本身就是一个大根堆,如果比上最小的根都是比它还小,那么就没有比较的必要了,直接break即可
        }
    }
    private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] =tmp;
    }
    //向上调整建堆的时间复杂度会很大,因为底层元素也得调整,而向下调整是不用调整底层元素的:O(n) = n*log2^n
    //TODO 堆的删除
    //1. 交换0下标和最后一个下标的值
    //2. 向下调整0下标这棵树就行
    public int pop() {
        //判空
        if(empty()) {
            //抛出异常
            throw new MyArrayListEmpty("堆为空!");
        }
        //记录原先的值
        int oldVal = elem[0];
        //交换第一个和最后一个元素
        swap(0,usedSize - 1);
        usedSize -- ;
        //向下调整0下标这棵树
        siftDown(0,usedSize);
        return oldVal;
    }
    public boolean empty() {
        return usedSize == 0;
    }

}
package 优先级队列_堆;

public class MyHeapIsEmpty extends RuntimeException{
    public MyHeapIsEmpty(String s) {
        super(s);
    }
}

4. java底层PriorityQueue常用接口介绍

        4.1 宏观了解PriorityQueue

                Java中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的PriorityBlockingQueue是线程安全的,我们主要介绍        

                

根据上面的图,我们可以知道PriorityQueue的几个特性:

        1. PriorityQueue中放置的类型必须是可以比较大小的

比如我们把Student自定义类放进入,因为没有实现Compareable接口,是无法进行比较的,因此无法构造堆

        2. 不能插入null对象

        3. 有自动扩容机制,可以任意插入多个元素(后续会说明扩容多少倍)

        4. 插入删除元素的的时间复杂度都是O(log2N) (二叉树的高度)

        5. PriorityQueue默认是小根堆,如果要转换为大根堆,我们需要自己写一个比较器(后面会说明)

        4.2 层源码的解释

                      4.2.1 构造方法  

                        我们这边介绍四种构造方法

                       我们的PriorityQueue默认是小根堆

构造器功能介绍
PriorityQueue()创建一个空的优先级队列,默认容量为11
PriorityQueue(int initialCapacity)用户自定义容量
PriorityQueue(Collection<? extends E> c)用一个集合来创建优先级队列
PriorityQueue(比较器)用户自己来设置大根堆

我们写比较器就先写一个类,然后我们需要实现Comparator接口,然后根据compareTo的返回值堆o1,o2进行排序.

具体代码:

class Imp implements Comparator<Integer> {

    @Override
    public int compare(Integer o1, Integer o2) {
        //TODO 主要是这个逻辑
//        return o1.compareTo(o2);//小根堆写法
        return o2.compareTo(o1);//大根堆写法
    }
}
//TODO 自己实现一个大根堆


    public static void main(String[] args) {
        Imp imp = new Imp();
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(imp);//传入比较器
        priorityQueue.offer(10);
        priorityQueue.offer(5);
        priorityQueue.offer(6);
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
    }
 static void TestPriorityQueue(){
// 创建一个空的优先级队列,底层默认容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 创建一个空的优先级队列,底层的容量为initialCapacity
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
        ArrayList<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(3);
        list.add(2);
        list.add(1);
// 用ArrayList对象来构造一个优先级队列的对象
// q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
    }

    public static void main(String[] args) {
        Test.TestPriorityQueue();
    }
    }

        4.3 增加元素的源码 

        

        4.4 扩容机制的解释

优先级队列的扩容说明:

如果容量小于64时,是按照oldCapacity的2倍方式扩容的

如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的

如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

        4.5 常用方法

        

函数名功能介绍
boolean offer(E e)插入元素e,插入成功返回true,对象为空抛出空指针异常,时间复杂度为O(log2N)
E peek()获得优先级最高的元素,队列为空返回null(但是元素本身还在优先级队列里面)
E poll()移除优先级 最高的元素并返回,如果为空返回null
int size()获取有效元素的个数
void clear()清空
boolean isEmpty()检测优先级队列是否为空,空返回true

具体实例

 public static void main(String[] args) {
            int[] arr = {4,1,9,2,8,0,7,3,6,5};
            //默认创建的是小根堆
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
            PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
            for (int e: arr) {
                q.offer(e);
            }
            System.out.println(q.size()); // 打印优先级队列中有效元素个数
            System.out.println(q.peek()); // 获取优先级最高的元素
// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
            q.poll();
            q.poll();
            System.out.println(q.size()); // 打印优先级队列中有效元素个数
            System.out.println(q.peek()); // 获取优先级最高的元素
            q.offer(0);
            System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
            q.clear();
            if(q.isEmpty()){
                System.out.println("优先级队列已经为空!!!");
    }else{
            System.out.println("优先级队列不为空");
        }
    }
//运行结果
10
0
8
2
0
优先级队列已经为空!!!

5. OJ题练习

          题目: 得到前k个最小的数:面试题 17.14. 最小K个数 - 力扣(LeetCode)

法1: 构造小根堆,出k次

步骤:

1.我们根据给定数组构造一个小根堆

2. 出队k次,每出一次元素我们就对0下标进行向下调整

具体代码:

 public static void main4(String[] args) {
    //建立小根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        int[] array = {10,3,15,7,19,9};
        //把数组元素放进堆里面,向上调整建立小根堆O(n*logn)
        for (int i = 0; i < array.length; i++) {
            priorityQueue.offer(array[i]);
        }
    //出k次O(n) = klogn
        int k = 3;
        int[] ret = new int[k];
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        System.out.println(Arrays.toString(ret));
    }

法2:构造大根堆,对顶和k后面的元素比

步骤:

1. 建立一个大根堆,把前k个元素放入大根堆

2. 让k后面的元素和堆顶元素进行比较,如果比根小,就把堆顶元素删除,加入该元素

具体代码:

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
    class Imp implements Comparator<Integer> {

    @Override
    public int compare(Integer o1, Integer o2) {
        //TODO 主要是这个逻辑
//        return o1.compareTo(o2);//小根堆写法
        return o2.compareTo(o1);//大根堆写法
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
                //创建一个大根堆,并且把前k个元素入栈
        //把比较器放进去,形成大根堆
        Imp imp = new Imp();
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(imp);
         int[] temp = new int[k];
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(arr[i]);
        }
        if(priorityQueue.peek() == null) {
            return temp;
        }
        //后面k+1后面下标的元素和大根堆堆顶进行比较
        for (int i = k; i < arr.length ; i++) {
            int com = priorityQueue.peek();
          
            if(com > arr[i]) {
                //如果比根小,出堆顶元素,并且把com加入到堆里面
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }
        //最后把大根堆元素取出来
    
        for (int i = 0; i < k; i++) {
            temp[i] = priorityQueue.poll();
        }
        return temp;
    }

}

6. java对象的比较

        6.1 基于==比较

基本数据类型直接比较的是值.

引用数据类型像String,我们比较的是地址.有点特殊,下面的代码会解释

自定义类型,我们比较的也是地址,但是,我们的地址是因为栈中的自定义类型指向的是堆中对象的地址,它们new出来时不同的.

        6.2 基于equals比较

注意我们如果重写equals方法,我们要设定自己比较的值,我们就按照以下步骤

1. 如果指向的是同一个对象就返回True
2. 如果传入的为null就返回false

3. 如果传入的不是比较类或者比较类的子类就返回false

4. 如果我们比较的值是引用类型的值,就要调用它自己的equals方法

注意: equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。
 

快速查找方法:ctr+fn+f 在搜索里面找

        6.3 基于Comparble比较

        我们自己自定义的类型,如果想比较出个大小,而不是判断等不等于,我们就在定义类的时候,实现Comparble接口,重写comparTo方法来进行设定比较的标准.

        6.4 基于比较器比较Comparator

        Comparble一旦写死就不能改了,不灵活,而我们的比较器Conparator就更加灵活,我们直接可以根据比较器里面的标准来对我们对象的值进行比较,并且对类的侵入性不强.我们先写一个比较类实现Comparator接口,重写compare方法.

 

总结:

        equals的返回值是boolean类型,其他俩个是int类型

        Comparable和Comparator的区别:前一种相当于写死了对类的侵入性强,后者就比较灵活对类侵入性弱

        一般写一个类,我们都要重写equals,实现Comparable,还要hashCode(后面会解释)

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2245184.html

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

相关文章

【AI】人工智能报告解读——中国人工智能的发展

自 2016 年 AlphaGo 与世界顶级围棋选手对战后&#xff0c;AI 概念和技术从此走入大众视野。2017 年&#xff0c;国务院颁布《新一代人工智能发展规划》&#xff0c;这是中国在人工智能领域第一个部署文件&#xff0c;确定了人工智能产业发展的总体思路、战略目标和任务。技术和…

Flutter:photo_view图片预览功能

导入SDK photo_view: ^0.15.0单张图片预览&#xff0c;支持放大缩小 import package:flutter/material.dart; import package:photo_view/photo_view.dart;... ...class _MyHomePageState extends State<MyHomePage>{overrideWidget build(BuildContext context) {return…

uni-app 修改复选框checkbox选中后背景和字体颜色

编写css&#xff08;注意&#xff1a;这个样式必须写在App.vue里&#xff09; /* 复选框 */ /* 复选框-圆角 */ checkbox.checkbox-round .wx-checkbox-input, checkbox.checkbox-round .uni-checkbox-input {border-radius: 100rpx; } /* 复选框-背景颜色 */ checkbox.checkb…

c++中mystring运算符重载

#include <iostream> #include <cstring>using namespace std;class mystring {char* buf; public:mystring(); //构造函数mystring(const char * str); //构造函数mystring(const mystring& str); //深拷贝函数void show(); //输出函数void setmystr(const my…

oracle数据恢复—通过拼接数据库碎片的方式恢复Oracle数据的案例

Oracle数据库故障&#xff1a; 存储掉盘超过上限&#xff0c;lun无法识别。管理员重组存储的位图信息并导出lun&#xff0c;发现linux操作系统上部署的oracle数据库中有上百个数据文件的大小变为0kb。数据库的大小缩水了80%以上。 取出&并分析oracle数据库的控制文件。重组…

SpringBoot中小企业人事管理系统:设计模式

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;中小企业人事管理系统当然也不能排除在外。中小企业人事管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理和…

git分支合并某一次提交

1.A分支&#xff1a;使用git log --oneline查看需要合并的id 或者直接去Git仓库查看提交记录 2.B分支&#xff1a;git cherry-pick id 合并A分支的请求

NFS文件服务器

持久化存储&#xff1a;NFS 1 NFS 工作原理 NFS&#xff08;Network File System&#xff09;是一种分布式文件系统协议&#xff0c;它允许用户在网络上通过一个网络共享访问文件&#xff0c;就如同访问本地存储一样。NFS 工作时&#xff0c;服务端将文件系统中的一个或多个目…

Springboot 整合 Java DL4J 搭建智能问答系统

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

Android kotlin之配置kapt编译器插件

配置项目目录下的gradle/libs.versions.toml文件&#xff0c;添加kapt配置项&#xff1a; 在模块目录下build.gradle.kt中增加 plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)// 增加该行alias(libs.plugins.jetbrains.kotl…

设计模式:4、命令模式(双重委托)

目录 0、定义 1、命令模式包括四种角色 2、命令模式的UML类图 3、代码示例 0、定义 将一个请求封装为一个对象&#xff0c;从而使用户可用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 1、命令模式包括四种角色 接…

详细教程-Linux上安装单机版的Hadoop

1、上传Hadoop安装包至linux并解压 tar -zxvf hadoop-2.6.0-cdh5.15.2.tar.gz 安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1u59OLTJctKmm9YVWr_F-Cg 提取码&#xff1a;0pfj 2、配置免密码登录 生成秘钥&#xff1a; ssh-keygen -t rsa -P 将秘钥写入认…

短剧系统小程序开发产品设计实例解析

短剧系统小程序开发架构深度解析引言 随着数字娱乐市场的蓬勃发展&#xff0c;短剧因其紧凑的情节、创新的表现形式和便捷的观看体验&#xff0c;迅速吸引了大量观众的关注。作为承载短剧内容的重要平台&#xff0c;短剧系统小程序不仅需要在用户体验、内容管理等方面做到极致&…

STM32--JLINK下载问题记录

1.下载提示&#xff1a; 现象&#xff1a;keil下载&#xff0c;会提示如上信息&#xff1b; 使用segger jflash可以连接成功&#xff0c;但是下载程序会失败&#xff1b; 解决过程&#xff1a;尝试一边复位一边下载程序&#xff0c;一直失败&#xff1b;STM32CubeProgrammer也…

推荐算法(基于用户/物品的协同过滤算法)

1.1 推荐算法概述 信息过载的时代。信息消费者面临的问题是如何收集到自己感兴趣的信息。对于信息生产者来说&#xff0c;高效地把信息推送给感兴趣的信息消费者&#xff0c;而不是淹没在信息互联网的海洋之中&#xff0c;也非常困难。 如何从大量的数据信息中获取有价值的信息…

【PCIE常见面试问题-1】

PCIE常见面试问题-1 1 PCIE概述1.1 PCI为何发展开PCIE&#xff1f;1.2 什么是Root Complex(RC)1.3 什么是EP&#xff1f;1.4 什么是Swith1.5 PCIE协议如何组织通信的&#xff1f;1.6 简要介绍一下PCIE的分层结构&#xff0c;为什么需要分层&#xff1f;1.7 PCIE的事务类型有哪些…

Easyexcel(5-自定义列宽)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09; 注解 ColumnWidth Data…

C++进阶:哈希表实现

目录 一:哈希表的概念 1.1直接定址法 1.2哈希冲突 1.3负载因子 1.4实现哈希函数的方法 1.4.1除法散列法/除留余数法 1.4.2乘法散列法 1.4.3全域散列法 1.5处理哈希冲突 1.5.1开放地址法 线性探测 二次探测 ​编辑 双重散列 1.5.2链地址法 二.代码实现 2.1开放地址…

汽车资讯新趋势:Spring Boot技术解读

5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 汽车资讯网站的系统管理员可以管理用户&#xff0c;可以对用户信息修改删除审核以及查询操作。具体界面的展示如图5.1所示。 图5.1 用户信息管理界面 5.1.2 汽车品牌管理 系统管理员可以汽车品牌信息进行添加&#xf…

EdgeNeXt:面向移动视觉应用的高效融合CNN-Transformer架构

摘要 https://arxiv.org/pdf/2206.10589 为了追求更高的准确性&#xff0c;通常会开发大型且复杂的神经网络。这些模型需要高计算资源&#xff0c;因此无法部署在边缘设备上。构建资源高效通用网络在多个应用领域都非常有用&#xff0c;因此备受关注。在本研究中&#xff0c;我…