【数据结构】优先级队列----堆

news2025/6/9 4:03:14

优先级队列----堆

  • 优先级队列
  • 堆的创建
    • 堆的插入:
    • 堆的删除:
  • PriorityQueue的特性
  • PriorityQueue的构造与方法

优先级队列

优先级队列: 不同于先进先出的普通队列,在一些情况下,优先级高的元素要先出队列。而这种队列需要提供两个基本的操作:返回最高优先级对象添加新的对象。
(JDK1.8中,优先级队列底层使用的是 堆 数据结构,而堆则是在完全二叉树的基础上进行了调整)

堆: 将一组集合的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 为 小堆,Ki >= K2i+1 且 Ki >= K2i+2 为 大堆。i = 0,1,2…,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。例如:

大小根堆示例
堆的两个性质:
堆中某个结点的值总是不大于或不小于其父节点的值。
堆总是一棵完全二叉树。


堆的存储方式:
堆是一棵完全二叉树,因此可以用层序的规则采用顺序的方式来高效存储,但对于非完全二叉树,就不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节
点,就会导致空间利用率比较低。

如果 i 表示孩子结点,则父节点为 (i - 1)/2。
如果 i 表示根结点,左孩子则为 2i + 1,右孩子为 2i + 2。

堆的创建

堆的调整
向下调整过程(以小根堆为例):

  1. 让 parent 标记需要调整的节点,child 标记 parent 的左孩子(完全二叉树中,一定先有左孩子)
  2. 如果 parent 的左孩子存在(child < size),进行以下操作,直到 parent 的左孩子不存在:
    找到左右孩子中较小的结点,和 parent 进行比较,若 parent 小,则调整结束。若 parent 大,则进行交换。交换后,可能会使原来满足堆的子树发生改变,所以需要继续向下调整。

例如:
向下调整过程
代码:

public class TestHeap {
    public int[] elem;
    public int usedSize;

    public TestHeap() {
        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() {
    	//循环调用
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(parent, usedSize);
        }
    }

    //向下调整 len为当前有效数据个数
    private void shiftDown(int parent, int len) {
        int child = 2 * parent + 1;
        //最起码 要有左孩子
        while (child < len) {
            //一定是有右孩子的情况下
            if (child + 1 < len && elem[child] > elem[child + 1]) {
                //保证 child指向较小值
                child++;
            }
            if (elem[child] < elem[parent]) {
                //孩子结点小于父节点 交换
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                //继续调整下面子树
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
}

建堆的时间复杂度:
建堆的时间复杂度

堆的插入:

堆的插入

代码:

public void offer(int val) {
        if (isFull()) {
            //满了扩容
            elem = Arrays.copyOf(elem, 2 * elem.length);
        }
        //放到最后一个位置,长度加一
        elem[usedSize++] = val;
        //向上调整
        shiftUp(usedSize-1);
    }

    public boolean isFull() {
        return usedSize == elem.length;
    }

    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (child > 0) {
            if (elem[child] < elem[parent]) {
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                //继续往上走
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }

堆的删除:

堆的删除
代码:

    public void pop() {
        if (isEmpty()) {
            return;
        }
        //交换
        int tmp = elem[0];
        elem[0] = elem[usedSize - 1];
        elem[usedSize - 1] = tmp;
        //有效数据个数减一
        usedSize--;
        //向下调整
        shiftDown(0, usedSize);
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }

PriorityQueue的特性

Java 集合框架中提供了 PriorityQueue 和 PriorityBlockingQueue 两种类型的优先级队列,PriorityQueue 是线程不安全的,PriorityBlockingQueue 是线程安全的,这里主要介绍PriorityQueue。

使用 PriorityQueue 需要注意:

  1. 使用时必须导入 PriorityQueue 所在的包(import java.util.PriorityQueue;)。

  2. PriorityQueue 中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException 异常。
    只插入一个元素时,并不会报错:
    添加一个元素
    插入多个元素就会报错:
    多个元素就会报错

  3. 不能插入 null 对象,否则会抛出 NullPointerException。
    不能插入null

  4. 内部可以自动扩容。
    自动扩容

  5. 插入和删除元素的时间复杂度为 O(log₂N)。

  6. PriorityQueue 底层使用的是堆数据结构。

  7. PriorityQueue 默认情况下是小堆。
    小堆

PriorityQueue的构造与方法

PriorityQueue 的几种构造方法:
构造方法

PriorityQueue 的方法:
PriorityQueue 的方法和其它数据结构类似:

PriorityQueue的方法

Top-k 问题: 最大或者最小的前k个数据。例如求一组数据中前 k 个最小的数据。

题目链接:leetcode----面试题 17.14. 最小K个数
题目描述:题目描述

思路一:我们可以初始化一个数组大小的堆,然后遍历数组的同时将元素放进堆中,默认是小根堆,所以取 k 次堆顶元素即可(删除堆顶元素后,会调整堆中的数据)。
代码:

public int[] smallestK(int[] arr, int k) {
    //存储最小K个数的数组
    int[] ret = new int[k];
    if (arr == null || k == 0) {
        return ret;
    }
    // 以数组长度初始化一个小根堆
    Queue<Integer> minHeap = new PriorityQueue<>(arr.length);
    //遍历数组  放进小根堆
    for (int value : arr) {
        minHeap.offer(value);
    }
    //取 k个堆顶元素
    for (int i = 0; i < k; i++) {
        ret[i] = minHeap.poll();
    }
    return ret;
}

上面这段代码会使时间复杂度升高,每添加或删除一个元素,就会调整一个接近数组长度的堆。

思路二:要求最小 k 个数,我们将数组里的前 k 个元素添加到一个大小为 k 的大根堆,因为是大根堆,所以堆顶元素是堆中最大的元素,然后遍历数组剩下的元素,如果数组剩下的元素比堆顶元素小,我们就删除堆顶元素,并添加数组的元素,如果数组剩下的元素比堆顶元素大,它就不会是前 k 个最小数。

代码:

public int[] smallestK(int[] arr, int k) {
    int[] ret = new int[k];
    if (arr == null || k == 0) {
        return ret;
    }
    //提供比较器,重写compare方法,此时建立的就是大根堆
    Queue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
    //将数组前k个元素添加到大根堆中
    for (int i = 0; i < k; i++) {
        maxHeap.offer(arr[i]);
    }
    //遍历数组未添加到堆中的元素
    for (int i = k; i < arr.length; i++) {
        //因为是求最小值,所以如果比大根堆的堆顶元素小,就删除堆顶元素,并添加这个元素到堆中
        if (arr[i] < maxHeap.peek()) {
            maxHeap.poll();
            maxHeap.offer(arr[i]);
        }
    }
    //将堆中的k个元素添加中数组中
    for (int i = 0; i < k; i++) {
        ret[i] = maxHeap.poll();
    }
    return ret;
}

这段代码中,只建立了 k 个容量大小的堆,调整的个数就比第一段代码少。

对于求一组数据中前 k 个最大或最小的元素时,数据少时我们可以排序,但对于数据特别多时,还是采用堆的方式比较合适。步骤如下:

  1. 用数据集合中前K个元素来建堆。
    求前 k 个最大的元素,则建小堆
    求前 k 个最小的元素,则建大堆
  2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。

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

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

相关文章

开启Openharmony 开发之旅

之前因为太懒&#xff0c;所以很少写博客。最近做了一年的鸿蒙开发。想记录下&#xff0c;故开始写点东西&#xff0c;作为学习和开发笔记吧&#xff01;先分享几个开源鸿蒙的学习网站。 1.开源鸿蒙官网 OpenAtom OpenHarmonyhttps://docs.openharmony.cn/pages/v3.1/zh-cn/a…

《MySQL系列-InnoDB引擎19》文件-日志文件-二进制日志

日志文件 日志文件记录了影响MySQL数据库的各种类型活动。MySQL数据库中常见的日志文件有&#xff1a; 错误日志(error log)二进制日志(bilog)慢查询日志(slow query log)查询日志(log) 这些日志文件可以帮助DBA对MySQL数据库的运行状态进行诊断&#xff0c;从而更好的进行数…

INTx中断机制源码分析

INTx中断机制源码分析 文章目录INTx中断机制源码分析参考资料&#xff1a;一、 配置空间二、 扫描设备时分配中断号三、 使用INTx中断四、 PCIe中断树五、 PCIe INTx中断映射过程5.1 PCIe控制器支持的中断5.2 PCIe控制器注册中断5.3 PCIe设备中断号的分配5.3.1 IRQ domain5.3.2…

Java源码程序设计-房屋出租管理系统设计与实现

摘 要系统设计系统实现开发环境摘 要 随着我国市场经济的快速发展和人们生活水平的不断提高&#xff0c;简单的房屋出租服务已经不能满足人们的需求。如何利用先进的管理手段&#xff0c;提高房屋出租的管理水平&#xff0c;是当今社会所面临的一个重要课题。 本文采用结构化…

Win10系统电脑开机后总是蓝屏无法使用怎么办?

Win10系统电脑开机后总是蓝屏无法使用怎么办&#xff1f;电脑开机的时候出现了蓝屏问题&#xff0c;这个情况是我们的电脑系统不兼容导致的。遇到这个问题一般是需要去进行系统的重装来解决&#xff0c;安装一个更兼容的系统就可以解决问题了。一起来看看详细的解决方法分享吧。…

前端学习第八站——CSS定位和装饰

目录 一、定位 1.1 网页常见布局方式 1.2 定位的常见应用场景 2.1 定位初体验 2.2 使用定位 3.1 静态定位 4.1 相对定位 5.1 绝对定位 6.1 子绝父相 7. 固定定位 8.1 定位的层级关系 8.2 更改定位元素的层级 9.总结 二、装饰 1.1 了解基线 1.2 文字对齐问…

安卓小游戏:小板弹球

安卓小游戏&#xff1a;小板弹球 前言 这个是通过自定义View实现小游戏的第三篇&#xff0c;是小时候玩的那种五块钱的游戏机上的&#xff0c;和俄罗斯方块很像&#xff0c;小时候觉得很有意思&#xff0c;就模仿了一下。 需求 这里的逻辑就是板能把球弹起来&#xff0c;球…

股票交易开放接口是什么意思?

在股票量化市场上&#xff0c;大家可能对股票交易开放接口的意思不太理解&#xff0c;其实换个角度来看&#xff0c;就是关于由开发团队进行开发的股票交易开放接口&#xff0c;那么对于接口的开发原理跟代码是怎么样的呢&#xff1f;一、股票交易开放接口函数的调用&#xff1…

面试的同学看这里!这套Java面试八股文,已经帮助200+人进入大厂

在看这篇文章之前&#xff0c;我想我们需要先搞明白八股文是什么&#xff1f;&#xff1f;&#xff1f; 明清科举考试的一种文体&#xff0c;也称制义、制艺、时文、八比文。八股文章就四书五经取题&#xff0c;内容必须用古人的语气&#xff0c;绝对不允许自由发挥&#xff0c…

Git 常用命令

一、基本的git命令 1、查看现在在哪个分支 git branch 2、切换到某分支 git checkout 某分支 3、添加修改后的代码到缓存区 git add . 4、添加提交代码的备注 git commit -m "注释" 5、提交代码到指定的分支 git push origin 某分支 6、从远程仓库克隆git仓库…

四 、QML常用控件的使用详解

在Qt Quick的世界里&#xff0c;window对象用于创建一个与操作系统相关的顶层窗口&#xff0c;而其他的元素&#xff0c;如Text Rectangle,Image等&#xff0c;都睡Windows提功能场景里面的显示对象&#xff0c;Window还有一个派生类&#xff0c;即是大名鼎鼎的Application Win…

基于DSP+FPGA高速运动控制器设计

基于“PC运动控制器”结构的开放式机器人运动控制系统能够充分利用PC开放程 度高、通用性好、处理能力强等特点以及运动控制器运算速度快、实时性能好、控制能 力强等特点&#xff0c;因此得到较快发展&#xff0c;成为目前的研究热点。但目前采用此种结构的开放式 机器人运动控…

3D模型深度生成网络【ShapeAssembly】

推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 我们提出了一个深度生成模型&#xff0c;该模型学习在ShapeAssembly中编写新颖的程序&#xff0c;ShapeAssembly是一种用于建模3D形状结构的特定领域语言。 执行 ShapeAssembly 程序会生成一个由部件代理长方体的分层连…

HashMap put() 方法源码分析

文章目录一、前置知识红黑树定义二、构造方法HashMap()HashMap(int initialCapacity, float loadFactor)tableSizeFor(int cap)&#xff1a;计算hashmap初始容量三、put 方法源码1. put()hash(Object key)&#xff1a;计算key的hash值2. putVal()通过 hash 计算数组下标3. resi…

jdk版本切换工具jenv使用指南

1.下载jenv包 下载链接&#xff1a;GitHub - FelixSelter/JEnv-for-Windows: Change your current Java version with one line 下载包的文件&#xff1a;JEnv.zip 然后解压缩&#xff0c;放到一个目录下&#xff0c;我这里放到了目录&#xff1a;D:\tools\JEnv 2.将JENV添…

chatGPT学习

最近看到一个火爆的AI智能聊天工具&#xff1a;ChatGPT。它的功能&#xff1a;文能写文章&#xff0c;武能改BUG&#xff0c;马斯克对它的评价是“Scary Good!”。我非常感兴趣&#xff0c;就试用了一下&#xff0c;感觉还不错&#xff0c;希望大家能喜欢。 ChatGPT&#xff0…

炼石完成近亿元A+轮融资,冲刺1500亿数据安全赛道

近日&#xff0c;炼石宣布完成近亿元A轮融资&#xff0c;本轮融资由重庆科技成果转化基金独家投资&#xff0c;由清科资本担任独家财务顾问&#xff0c;这是继安天科技、安云资本、国科嘉和、腾讯等多轮之后的新一轮投资。随着本轮资金的引入&#xff0c;炼石将更深入研发迭代以…

MASA Stack 1.0 发布会讲稿 —— 产品篇

架构 基于MASA的云原生技术架构 我们基于MASA去做了一个云原生技术架构&#xff0c;左下角的MASA Blazor主要是为我们去集成一个多端的UI能力&#xff0c;具体的使用场景将在实践篇为大家介绍——MASA Blazor如何去做多端的 接着就是集成非业务能力的MASA Framework&#xff…

1.Docker 简介

Docker 简介 什么是 Docker&#xff1f; Docker的英文翻译是“搬运工”的意思&#xff0c;他搬运的东西就是我们常说的集装箱Container&#xff0c;Container 里面装的是任意类型的 App&#xff0c;我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的…

C语言位运算

所谓位运算&#xff0c;就是对一个比特&#xff08;Bit&#xff09;位进行操作。比特&#xff08;Bit&#xff09;是一个电子元器件&#xff0c;8个比特构成一个字节&#xff08;Byte&#xff09;&#xff0c;它已经是粒度最小的可操作单元了。C语言提供了六种位运算符&#xf…