【Java数据结构】——详解优先级队列-(堆)

2023-11-09


一、堆的概念

堆的定义:n个元素的序列{k1 , k2 , … , kn}称之为堆,当且仅当满足以下条件时:

(1)ki >= k2i 且 ki >= k(2i+1) ——大根堆
(2) ki <= k2i 且 ki <= k(2i+1) ——小根堆

简单来说:

堆是具有以下性质的完全二叉树:
(1)每个结点的值都大于或等于其左右孩子结点的值,称为大根堆(如左下图);
或者:
(1)每个结点的值都小于或等于其左右孩子结点的值,称为小根堆(如右下图)。

在这里插入图片描述

我们使用数组保存二叉树结构,即是将二叉树用层序遍历方式放入数组中,如上图。

堆的元素下标存在以下关系:

  1. 假如已知双亲(parent)的下标,则
    左孩子(left)下标 = 2parent + 1;
    右孩子(right)下标 = 2
    parent +2;
    2.已知孩子(child)(不区分左右)下标,则:
    双亲(parent)下标 = (child - 1)/ 2 ;

小结:

  1. 堆逻辑上是一棵完全二叉树;
  2. 堆物理上保存在数组中;
  3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;反之,则是小堆,或者小根堆,或者最小堆;
  4. 堆的基本作用是,快速找集合中的最值。

二、向下调整

1.建初堆

设有一个无序序列 {2,5,7,8,4,6,3,0,9,1 },下面通过图解来建初始堆。

这里有一个前提:这棵二叉树的左右子树都必须是一个堆,才能进行调整。
下面是用到的数据的一些说明:

  1. array 代表存储堆的数组
  2. size 代表数组中被视为堆数据的个数
  3. index 代表要调整位置的下标
  4. left 代表 index 左孩子下标
  5. right 代表 index 右孩子下标
  6. min 代表 index 的最小值孩子的下标

过程文字描述如下:

  1. index 如果已经是叶子结点,则整个调整过程结束:
    (1)判断 index 位置有没有孩子;
    (2) 因为堆是完全二叉树,没有左孩子就一定没有右孩子,所以判断是否有左孩子;
    (3) 因为堆的存储结构是数组,所以判断是否有左孩子即判断左孩子下标是否越界,即 left >= size 越界。
  2. 确定 left 或 right,谁是 index 的最小孩子 min:
    (1) 如果右孩子不存在,则 min = left;
    (2)否则,比较 array[left] 和 array[right] 值得大小,选择小的为 min;
    (3)比较 array[index] 的值 和 array[min] 的值,如果 array[index] <= array[min],则满足堆的性质,调整结束。
  3. 否则,交换 array[index] 和 array[min] 的值;
  4. 然后因为 min 位置的堆的性质可能被破坏,所以把 min 视作 index,向下重复以上过程。

通过上面的操作描述,我们写出以下代码:

public static void shiftDown(int[] array, int size, int index){
        int left = 2*index +1;
        while(left < size){
            int min = left;
            int right = 2*index +2;
            if(right<size){
                if(array[right] < array[left]){
                    min = right;
                }
            }
            if(array[index] <= array[min]){
                break;
            }
            int tmp = array[index];
            array[index] = array[min];
            array[min] = tmp;
            index = min;
            left = 2*index +1;
        }
    }

时间复杂度为 O(log(n))。

2.建堆

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

在这里插入图片描述时间复杂度分析:
粗略估算,可以认为是在循环中执行向下调整,为 O(n * log(n)),(了解)实际上是 O(n)。

//建堆代码
    public void createHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
        //根据代码 显示的时间复杂度   看起来 应该是O(n*logn)  但是 实际上是O(n)
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            //调整
            shiftDown(parent,usedSize);
        }
    }

三、优先级队列

1.什么是优先队列?

根据百科解释:

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出(first in, largest out)的行为特征。通常采用堆数据结构来实现。

所以我们在这里实现优先队列的内部原理是堆,也就是说采用堆来构建。

2.入队列

过程(以大堆为例):

  1. 首先按尾插方式放入数组;
  2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束;
  3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤;
  4. 直到根结点。

下面图解:
在这里插入图片描述

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

3.出队列

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向 下调整方式重新调整成堆。

在这里插入图片描述

    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 offer(int val) {
        if(isFull()) {
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        //注意这里传入的是usedSize-1
        shiftUp(usedSize-1);
    }

4.返回队首元素

直接返回堆顶元素

    public int peek() {
        if(isEmpty()) {
            throw new RuntimeException("优先级队列为空!");
        }
        return elem[0];
    }

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

整体的代码:

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

    public TestHeap() {
        this.elem = new int[10];
    }

    /**
     * 向下调整函数的实现
     * @param parent 每棵树的根节点
     * @param len 每棵树的调整的结束位置  10
     */
    public void shiftDown(int parent,int len) {
        int child = 2*parent+1;
        //1、最起码 是有左孩子的,至少有1个孩子
        while (child < len) {
            if(child+1 < len && elem[child] < elem[child+1]) {
                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 createHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
        //根据代码 显示的时间复杂度   看起来 应该是O(n*logn)  但是 实际上是O(n)
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            //调整
            shiftDown(parent,usedSize);
        }
    }

    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 offer(int val) {
        if(isFull()) {
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        //注意这里传入的是usedSize-1
        shiftUp(usedSize-1);
    }

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

    public int poll() {
        if(isEmpty()) {
            throw new RuntimeException("优先级队列为空!");
        }
        int tmp = elem[0];
        elem[0] = elem[usedSize-1];
        elem[usedSize-1] = tmp;
        usedSize--;
        shiftDown(0,usedSize);
        return tmp;
    }


    public int peek() {
        if(isEmpty()) {
            throw new RuntimeException("优先级队列为空!");
        }
        return elem[0];
    }

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


    public void heapSort() {
        int end = this.usedSize-1;
        while (end > 0) {
            int tmp = elem[0];
            elem[0] = elem[end];
            elem[end] = tmp;
            shiftDown(0,end);
            end--;
        }
    }

}

5.堆的其他TopK问题

什么是TopK问题?
从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。

解决这类问题,我们往往会有以下几种思路:

  1. 对整体进行排序,输出前10个最大的元素。
  2. 用上面刚刚讲的堆。
  3. 也是用堆,不过这比第二个思路更巧妙。

我们直接讲思路三:

  1. 先用前k个元素生成一个小顶堆,这个小顶堆用于存储,当前最大的k个元素。
  2. 接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。
  3. 直到,扫描完所有n-k个元素,最终堆中的k个元素,就是所要求的TopK。

以这个数组{12,15,21,41,30}为例,找到前3个最大的元素。

在这里插入图片描述
那如果是将一组进行从小到大排序,我们该采用大根堆还是小根堆?

答案是:大根堆!

步骤如下:

  1. 将这组数据调整为大根堆调整为大根堆;
  2. 0下标和最后1个未排序的元素进行交换即可;
  3. 重复1、2,直到结束。

在这里插入图片描述

总结:

  1. 如果求前K个最大的元素,要建一个小根堆。
  2. 如果求前K个最小的元素,要建一个大根堆。
  3. 第K大的元素。建一个小堆,堆顶元素就是第K大的元素。
  4. 第K小的元素。建一个大堆,堆顶元素就是第K小的元素。
    public void heapSort() {
        int end = this.usedSize-1;
        while (end > 0) {
            int tmp = elem[0];
            elem[0] = elem[end];
            elem[end] = tmp;
            shiftDown(0,end);
            end--;
        }
    }
    public void shiftDown(int parent,int len) {
        int child = 2*parent+1;
        //1、最起码 是有左孩子的,至少有1个孩子
        while (child < len) {
            if(child+1 < len && elem[child] < elem[child+1]) {
                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;
            }
        }
    }



总结

来来回回,这篇文章写了2-3天了,以前写文章总是蜻蜓点水,不到水深,导致自己对很多的知识也没有多深理解,仅仅是为了写文章而写文章。希望有改变,从这篇文章开始吧!

如文章有错误,还请各位看客老爷斧正!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Java数据结构】——详解优先级队列-(堆) 的相关文章

  • 如何在测试套件中定义 JUnit 方法规则?

    我有一个类 它是 JUnit 测试类的 JUnit 套件 我想定义一个规则on the suite 这是可以做到的 但需要做一些工作 您还需要定义自己的 Suite 运行程序和测试运行程序 然后在测试运行程序中重写 runChild 使用以
  • 任务“:app:dexDebug”执行失败

    我目前正在处理我的项目 我决定将我的 Android Studio 更新到新版本 但在我导入项目后 它显示如下错误 Information Gradle tasks app assembleDebug app preBuild UP TO
  • Android - 如何访问 onResume 中 onCreate 中实例化的 View 对象?

    In my onCreate 方法 我正在实例化一个ImageButton View public void onCreate Bundle savedInstanceState super onCreate savedInstanceSt
  • java中的csv到pdf文件

    我正在尝试获得一个csv文件解析为pdf 到目前为止我所拥有的内容附在下面 我的问题是这段代码最终出现在 pdf 中的文件在 csv 文件的第一行被截断 我不明白为什么 附示例 本质上我想要一个没有任何操作的 csv 文件的 pdf 版本
  • 如何解决 onEditCommit 事件上的类型不匹配错误?

    我在 Fxml 中使用 onEditCommit 事件在用户编辑数据后检索数据 FXML 代码
  • 方法断点可能会大大减慢调试速度

    每当向方法声明行添加断点 在 Intellij IDEA 或 Android Studio 中 时 都会出现一个弹出窗口 方法断点可能会大大减慢调试速度 为什么会这样戏剧性地减慢调试速度 是我的问题吗 将断点放在函数的第一行有什么不同 Th
  • Android - 除了普通 SSL 证书之外还验证自签名证书

    我有一个通过 SSL 调用 Web 服务的 Android 应用程序 在生产中 我们将拥有由受信任的 CA 签名的普通 SSL 证书 但是 我们需要能够支持自签名证书 由我们自己的 CA 签名 我已经成功实施了接受自签名证书的建议解决方案
  • 使用 Java 在浏览器中下载 CSV 文件

    我正在尝试在 Web 应用程序上添加一个按钮 单击该按钮会下载一个 CSV 文件 该文件很小 大小仅约 4KB 我已经制作了按钮并附加了一个侦听器 文件也准备好了 我现在唯一需要做的就是创建单击按钮时下载 csv 文件的实际事件 假设 fi
  • 如何将 XMP XML 块序列化为现有的 JPEG 图像?

    我有许多 JPEG 图像 其中包含损坏的 XMP XML 块 我可以轻松修复这些块 但我不确定如何将 固定 数据写回图像文件 我目前正在使用 JAVA 但我愿意接受任何能让这项任务变得容易的事情 这是目标关于 XMP XML 的另一个问题
  • 为什么我在 Mac 上看到“java.lang.reflect.InaccessibleObjectException: Unable to make private java.nio.DirectByteBuffer(long,int)accessibl

    我已经在工作中愉快地构建代码好几天了 但突然我的一个项目 不是全部 失败并出现此错误消息 看看下面的答案吧 我是如何修复它的 起初我用谷歌搜索 看到很多有这个问题的人正在使用 Java 16 但我认为 错误 我正在使用 Java 11 因为
  • 想要开发像 Facebook 这样的网站 - 处理数百万个请求 - 高性能 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我想用 Java 开发一个像 Fac
  • Java:VM 如何在 32 位处理器上处理 64 位“long”

    JVM 如何在 32 位处理器上处理 64 位的原始 long 在多核 32 位机器上可以并行利用多个核心吗 64 位操作在 32 位机器上慢了多少 它可能使用多个核心来运行不同的线程 但不会并行使用它们进行 64 位计算 64 位长基本上
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • 具有多种值类型的 Java 枚举

    基本上我所做的是为国家编写一个枚举 我希望不仅能够像国家一样访问它们 而且还能够访问它们的缩写以及它们是否是原始殖民地 public enum States MASSACHUSETTS Massachusetts MA true MICHI
  • 从 html 页面和 javascript 调用 java webservice

    我正在尝试从 javascript 调用 java 实现的 Web 服务 使用 NetBeans IDE 我读过很多关于 jQuery 和 AJAX 的内容 但我似乎无法掌握它 假设我的 Web 服务 WSDL 位于 http localh
  • 从 Stax XMLStreamReader 读取以解组部分

    我正在使用 Stax 游标 API 从大型 xml 文件中提取数据 当前 我转到特殊标签的开头并使用 JAXB 解组该标签 这对于格式良好的 xml 文件效果很好 但不久前我有一个文档 其中数十万个标签中有一个未关闭 JAXB 使用 XML
  • 如何在android sdk上使用PowerMock

    我想为我的 android 项目编写一些单元测试和仪器测试 然而 我遇到了一个困扰我一段时间的问题 我需要模拟静态方法并伪造返回值来测试项目 经过一些论坛的调查 唯一的方法是使用PowerMock来模拟静态方法 这是我的 gradle 的一
  • 如何移动图像(动画)?

    我正在尝试在 x 轴上移动船 还没有键盘 我如何将运动 动画与boat png而不是任何其他图像 public class Mama extends Applet implements Runnable int width height i
  • 为什么 BufferedWriter 不写入文件?

    我有这个代码 String strings Hi You He They Tetrabenzene Caaorine Calorine File file new File G words txt FileWriter fWriter Bu
  • Spring Boot MSSQL Kerberos 身份验证

    目前在我的春季靴子中application properties文件中 我指定以下行来连接到 MSSql 服务器 spring datasource url jdbc sqlserver localhost databaseName spr

随机推荐

  • FASTJSON和JACKSON基本使用

    Json是一种轻量级的数据交换格式 采用一种 键 值 对的文本格式来存储和表示数据 在系统交换数据过程中常常被使用 是一种理想的数据交换语言 在使用Java做Web开发时 不可避免的会遇到Json的使用 JSON形式与语法 JSON对象 我
  • linux使用xe命令管理远程xenserver机器

    1 linux下安装xe工具 xenserver6 2的dom0是32位centos5系统 xenserver6 5是64位centos系统 xe工具都是以rpm包形式提供 在ubuntu下安装时 先要用alien转换rpm包为deb包 注
  • group by的使用场景

    group by的使用场景 自我理解 一般配合sum使用 用来分组统计总 量 面试的时候最好说我们当时用的group 加sum查询每个sku的销量 group by 分组 查询 就是把记录集中的记录按一定规则进行 分组统计 假设一个学生名单
  • Android 关于inflate

    通俗的说 inflate就相当于将一个xml中定义的布局找出来 因为在一个Activity里如果直接用findViewById 的话 对应的是setConentView 的那个layout里的组件 因此如果你的Activity里如果用到别的
  • AWK入门到精通系列——awk快速入门

    简介 AWK是一个优良的文本处理工具 Linux及Unix环境中现有的功能最强大的数据处理引擎之一 这种编程及数据操作语言 其名称得自于它的创始人阿尔佛雷德 艾侯 彼得 温伯格和布莱恩 柯林汉姓氏的首个字母 的最大功能取决于一个人所拥有的知
  • nrm下载报错(C:\Users\16500\AppData\Roaming\npm\node_modules\nrm\cli.js:9const open = require(‘open‘)问题解决

    问题报错 建议大家下载nrm的时候尽量在node js 16版本之上 不然会造成很多报错问题无法解决 node 16版本下载地址 node js 16 问题原因是应该使用 open 的 CommonJs规范的包 现在 open v9 0 0
  • 华为云计算01——虚拟化技术

    云计算概述 云计算不是一个新的技术 它是通过虚拟化技术 将物理硬件资源虚拟化成为多个能独立运行且相同的 能为多个虚拟机提供服务 最开始云计算由亚马逊提出 并完成了云计算的初步架构 但是 亚马逊云是私有的 未公布其代码与实现原理 而后来开源社
  • Flutter drawer 侧滑实现二(通过点击现实显示侧滑栏)

    1 首先在需要使用的页面加入下方代码 GlobalKey
  • FreeRTOS(1):任务

    目录 一 FreeRTOS 介绍 什么是 FreeRTOS 为什么选择 FreeRTOS FreeRTOS 资料与源码下载 FreeRTOS 实现多任务的原理 二 移植 FreeRTOS 到STM32 手动移植 使用CubeMX快速移植 快
  • printf() 函数不加 \n 无法及时输出

    for printf worker进程休息1秒 sleep 1 printf 函数末尾不加 n 就无法及时地将信息显示到屏幕上 这是因为行缓存 Windows上一般没有 类Unix上才有 行缓存 需要输出的数据不直接显示到终端 而是首先缓存
  • 关于详解Java的对象创建

    前言 在 还不清楚怎样面向对象 和 面向对象再探究 两篇文章中 都介绍了关于面向对象程序设计的概念和特点 其中也涉及到了许多代码 比如 Dog dog new Dog 这篇文章就主要来谈谈创建对象时的具体操作 2 引入例子 下面是一个Dog
  • 洛谷 P5710 数的性质

    题目描述 一些数字可能拥有以下的性质 性质 1 是偶数 性质 2 大于 4 且不大于 12 小A 喜欢这两个性质同时成立的数字 Uim 喜欢这至少符合其中一种性质的数字 八尾勇喜欢刚好有符合其中一个性质的数字 正妹喜欢不符合这两个性质的数字
  • VR开发——Unity中导入常用的VR开发插件及简单使用

    VR开发 Unity中导入VIVE的VR开发插件及简单使用 V客学院 今天我们来讲解如何进行简单的htc vive设备的软体开发 今天的教程主要讲解从插件的导入到基本的设置以及场景搭建 小白向 首先 我们需要进入Unity中的AssetsS
  • MySQL explain 、explain extended用法

    explain显示了mysql如何使用索引来处理select语句以及连接表 可以帮助选择更好的索引和写出更优化的查询语句 使用方法 在select语句前加上explain就可以了 如 explain select from statuses
  • Unity基础之Vuforia

    Vuforia官网 https developer vuforia com Vuforia Agumented Reality SDK Vuforia增强现实扩展工具包 所属公司 高通 支持平台 Android IOS UWP 支持设备 前
  • 基于langChain 的privateGPT 文档问答 研究

    参考 gihtub代码 https github com imartinez privateGPT 官网 privateGPT可以在断网的情况下 借助GPT和文档进行交互 有利于保护数据隐私 privateGPT可以有四个用处 1 增强知识
  • [nodejs]解决mysql和连接池(pool)自动断开问题

    最近在做一个个人项目 数据库尝试使用了mongodb sqlite和mysql 分享一下关于mysql的连接池用法 项目部署于appfog 项目中我使用连接池链接数据库 本地测试一切正常 上线以后 经过几次请求两个数据接口总是报503 一直
  • stm32_acs712电流采集计算思路

    Acs712数据手册地址 https item szlcsc com 45473 html 需要测量的参数 0 实际电流值 ACS712 A 1 acs712供电电压 Vin 2 ACS 输出电压 712 OUT V 3 ACS 输出电压
  • ps命令查看具体进程的所有线程

    ps p PID t
  • 【Java数据结构】——详解优先级队列-(堆)

    文章目录 一 堆的概念 二 向下调整 1 建初堆 2 建堆 三 优先级队列 1 什么是优先队列 2 入队列 3 出队列 4 返回队首元素 5 堆的其他TopK问题 总结 一 堆的概念 堆的定义 n个元素的序列 k1 k2 kn 称之为堆 当