【java数据结构-优先级队列向下调整Topk问题,堆的常用的接口详解】

在这里插入图片描述

🌈个人主页:努力学编程’
个人推荐:基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解
学好数据结构,刷题刻不容缓:点击一起刷题
🌙心灵鸡汤总有人要赢,为什么不能是我呢
在这里插入图片描述

相信大家对于队列的理解是比较熟悉的,队列是一种先进先出的数据结构,但是我们在日常项目的开发中往往需要对于一些数据的处理是要求所使用的数据结构处理数据时必须有一定的优先级的,比如你在打游戏的时候,字节给你打入职电话,那么手机一定会优先处理电话的业务,或者在你的学生时代,一定经历过老师让学习好的同学先挑座位,类似与这种情况,我们引出一个新的数据结构优先级队列-堆

🌈堆的定义:

堆是一种特殊的树形数据结构,它满足堆属性:对于堆中任意节点 i,父节点的值小于等于(或大于等于)其子节点的值。根据这个属性,可以将堆分为最大堆和最小堆。在最大堆中,父节点的值大于等于其子节点的值;在最小堆中,父节点的值小于等于其子节点的值。

堆通常用于实现优先级队列,因为堆能够快速找到具有最高(或最低)优先级的元素。常见的堆有二叉堆和斐波那契堆,它们在插入和删除操作的时间复杂度上有所不同,但都能保持堆属性

大根堆:

  • 顾名思义:根节点其实是整个完全二叉树的最大值的节点,他的左孩子结点和右孩子节点都比他要小,同时要保证整棵树是大根堆那么每棵子树也必须得是大根堆,这样才可以构成一个完整的大根堆结构。

在这里插入图片描述

小根堆:

  • 有了大根堆理解,我们去学习小根堆就轻松多了,每棵子树的根节点都是这棵子树中的值最小的,这样所有的子树就构成了整个小根堆。
    在这里插入图片描述
  • 有了小根堆和大根堆的概念,我们后面可以尝试自己实现一个小根堆和大根堆,后面会给大家实现。请大家牢记这些基本知识。

🌝堆的创建

  • 讲了堆的一些基本知识,相信大家都和我一样,手痒难耐,非常想自己手搓一个堆,但是在这之前我们必须要先了解一个知识点向下调整,这是我们实现队列的基本要求。

向下调整:

在这里插入图片描述
1. 让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)

2. 如果parent的左孩子存在,即:child < size, 进行以下操作,直到parent的左孩子不存在parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child进行标将parent与较小的孩子child比较,如果

  • parent小于较小的孩子child,调整结束,

  • 否则:交换parent与较小的孩子child,交换完成之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child;child = parent*2+1; 然后继续2。

具体的过程建议大家,在纸上先自己实现一下,这里把整个过程也给大家:
在这里插入图片描述

public void shiftDown(int[]array,int parent){int child=2*parent+1;int size= array.length;while(child<size){if(child+1<size&&array[child+1]<array[child]){child+=1;}if(array[parent]<=array[child]){break;}else{int t=array[parent];array[parent]=array[child];array[child]=t;}parent=child;child=parent*2+1;}}
  • 好了掌握了向下调整的知识点,我们就可以自己手搓一个堆了,我们实现的代码是比较简单的。针对每个节点我们都使用,向下调整的逻辑,就可以实现大根堆或者小根堆,
public static void createHeap(int[] array) {
// 找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,遇到一个节点,应用向下调整
int root = ((array.length-2)>>1);
for (; root >= 0; root--) {
shiftDown(array, root);
}
public void shiftDown(int[]array,int parent){int child=2*parent+1;int size= array.length;while(child<size){if(child+1<size&&array[child+1]<array[child]){child+=1;}if(array[parent]<=array[child]){break;}else{int t=array[parent];array[parent]=array[child];array[child]=t;}parent=child;child=parent*2+1;}}
}

🚴堆的时间复杂度:

因为初始化堆,是每个数据向下冒泡,每一层冒泡的次数不同,最高层冒泡的次数最多为logn,最底层为1次,设共有h层(h=logn) , 时间复杂度的计算为:1h+2(h-1)+4(h-2)+…+2^(h-1)1

即【节点数所在层数】的累加,如2(h-1)即第二层有两个节点,因为在第二层,所以会冒泡h-1次

可以转化为通项:
k从0到h-1的累加 用错项相减法求 可以得到O(),即O()即O(n)
关于调整堆/删除堆

因为在调整的时候,每调整/删除一次,树的节点就会少一个(跑到有序堆去了),然鹅每次调整的时候,是从下往上冒泡的,所以调整的次数即当前树的层数,那怎么确定当前树的层数呢,直接对当前节点取对数就好了,每一个节点都要进行这样的操作,所以操作的次数是log(n-1)+log(n-2)+…+log2+log1=log(n-1)!=nlogn

故时间复杂度为O(nlogn)

故整个堆排序 时间复杂度取大的O(nlogn)

⛅堆的插入和删除

插入:

大家有没有想过在堆里面如何添加节点呢,整个过程大致分为两个步骤,

  • 将待插入的节点放到二叉树的最后一个叶子节点
  • 从待插入的节点开始依次进行shiftUp向上调整

在这里插入图片描述

具体向上调整代码如何实现,这里也给大家一段代码

public void shiftUp(int[] array,int child){//这里模拟的是大根堆的情况int parent=(child-1)/2;while(child>0){if(array[parent]>array[child]){break;}else {int t=array[child];array[parent]=array[child];array[child]=t;child=parent;parent=(child-1)/2;}}}

删除:

  • 对于堆的删除是十分重要的,想想我们在文章开始的时候,是不是说过,堆的底层逻辑是一种线性结构,本质上我们可以使用数组实现,那么如果把他转化为数组的时候,我们知道数组的元素其实是不能删除掉的,只能对数据进行覆盖,来完成元素的删除。堆在这里也是一样。

堆删除的具体步骤如下:

  • 将待删除的元素与二叉树的最后一个叶子结点进行交换
  • 将堆的底层数组内的有效数据个数减一
  • 把整个二叉树进行向下调整,重新调整二叉树的结构实现大根堆或者小根堆

在这里插入图片描述

 public int poll() {if(isEmpty()) {return -1;}int old = elem[0];swap(0,usedSize-1);usedSize--;siftDown(0,usedSize);return old;}

有关堆的一些常见接口介绍:

在java中我们并不需要自己去实现堆,java是一门高级语言,我们只需要知道每种重要的数据结构底层是如何实现的,然后我们直接使用系统为我们提供的结构就好了。

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

在这里插入图片描述
关于PriorityQueue的使用要注意:

  1. 使用时必须导入PriorityQueue所在的包,即:
java import java.util.PriorityQueue;
  1. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较 大小的对 象,否则会抛出ClassCastException异常
  2. 不能插入null对象,否则会抛出NullPointerException
  3. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  4. 插入和删除元素的时间复杂度为O( l o g n logn logn)
  5. PriorityQueue底层使用了堆数据结构
  6. PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素

当然我们也要熟悉对于堆的一些基础操作:

在这里插入图片描述

static void TestPriorityQueue2(){
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("优先级队列不为空");
}
}

优先级队列的扩容说明:

  • 如果容量小于64时,是按照oldCapacity的2倍方式扩容的
  • 如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的
  • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

堆的应用-TopK问题

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都
不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

我们最先想到的,就是创建一个小根堆把前k个元素放进去,然后接着遍历数组将较小的元素换进去,最后将堆的元素放到一个数组中返回这个数组就好

class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}
}
public class Test {/*虽然 通过了 但是 不是非常好的解决方案*/public static int[] smallestK1(int[] arr, int k) {PriorityQueue<Integer> minHeap = new PriorityQueue<>();for (int i = 0; i < arr.length; i++) {minHeap.offer(arr[i]);}int[] tmp = new int[k];for (int i = 0; i < k; i++) {int val = minHeap.poll();tmp[i] = val;}return tmp;}

这段代码虽然可以提交成功,但是并非是最优的解法,最规范的解法,是这样的:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆

  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}
}
class Solution {public static int[] smallestK(int[] arr, int k) {int[] tmp = new int[k];if(k == 0) {return tmp;}PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());//1.把前K个元素放进堆中for (int i = 0; i < k; i++) {maxHeap.offer(arr[i]);}//2.遍历剩下的N-K个元素for (int i = k; i < arr.length; i++) {int val = maxHeap.peek();if(val > arr[i]) {maxHeap.poll();maxHeap.offer(arr[i]);}}for (int i = 0; i < k; i++) {tmp[i] = maxHeap.poll();}return tmp;}
}

好了,今天就分享到这里,期待大家的一键三连!!!

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

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

相关文章

OpenHarmony实战开发-媒体查询 (@ohos.mediaquery)

概述 媒体查询作为响应式设计的核心&#xff0c;在移动设备上应用十分广泛。媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。媒体查询常用于下面两种场景&#xff1a; 针对设备和应用的属性信息&#xff08;比如显示区域、深浅色、分辨率&#xff09;&#xff0…

异步日志方案spdlog

异步日志方案spdlog spdlog 是一款高效的 C 日志库&#xff0c;它以其极高的性能和零成本的抽象而著称。spdlog 支持异步和同步日志记录&#xff0c;提供多种日志级别&#xff0c;并允许用户将日志输出到控制台、文件或自定义的接收器。 多线程使用和同步、异步日志没有关系是…

Linux系统----信号(万字文章超级详细并且简单易学附有实操shell指令图及注释!)

绪论​ “Do one thing at a time, and do well.”&#xff0c;本章开始Linux系统其中信号是学习操作系统的基本下面将会讲到什么是信号、信号的多种产生方式、信号如何保存的、信号如何处理的、以及一些信号的细节。话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑…

【鸿蒙】通知

一、概要 Android的Notification。 说到通知&#xff0c;就想到了推送。 通知这块可以做到不像Android一样需要集成各家厂商的推送了&#xff0c;不知道是否有建立独立的推送系统 这是官网上介绍的跨APP进行的IPC通知。实际在Android开发过程中&#xff0c;可能这种场景会相对…

MarginNote 3 for Mac:一站式思维导图与笔记神器,让学习更高效

MarginNote 3 for Mac是一款功能强大的阅读和学习工具软件&#xff0c;它将PDF/EPUB阅读器和多种学习工具集成起来&#xff0c;旨在帮助用户更有效地进行阅读、笔记整理以及知识管理。 这款软件的核心功能在于其能够将阅读与学习过程紧密结合。用户可以在阅读文档时&#xff0…

勒索软件安全防护手册

文章目录 相关背景勒索软件概述勒索软件主要类型文件加密类勒索软件数据窃取类勒索软件系统加密类勒索软件。屏幕锁定类勒索软件 勒索软件典型传播方式利用安全漏洞传播利用钓鱼邮件传播利用网站挂马传播利用移动介质传播利用软件供应链传播利用远程桌面入侵传播 典型勒索软件攻…

自动驾驶传感器篇: GNSSIMU组合导航

自动驾驶传感器篇&#xff1a; GNSS&IMU组合导航 1.GNSS1.1 GNSS 系统概述1.2 GNSS系统基本组成1. 空间部分&#xff08;Space Segment&#xff09;&#xff1a;2. 地面控制部分&#xff08;Ground Control Segment&#xff09;&#xff1a;3. 用户设备部分&#xff08;Use…

Stable Diffusion WebUI 使用 VAE 增加滤镜效果

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要介绍 VAE 模型&#xff0c;主要内容有&#xff1a;VAE 模型的概念、如果下载 VAE 模型、如何安装 VAE 模型、如…

开箱展示——深圳市雷龙发展的存储卡

最近收到了来自深圳市雷龙发展有限公司寄来的存储卡&#xff0c;奈何最近也没有好的嵌入式项目需要用到&#xff0c;哪这里就简单给大家展示一下吧。 原始包装大概就是这样子了垃&#xff0c;有两个存储芯片和一个简单的转接器&#xff0c;测试的时候可以把芯片焊接到转接器…

如何安装mysl驱动程序jar包

简介&#xff08;为什么要安装mysql驱动jar包&#xff09; MySQL 驱动程序&#xff08;通常以 JAR 文件的形式提供&#xff09;用于在 Java 应用程序中连接和与 MySQL 数据库进行交互。这些驱动程序提供了一组 API&#xff0c;使 Java 应用程序能够执行诸如查询、插入、更新和…

【月报】​Aavegotchi 开发更新 |2024 年 4 月版,多款游戏上新玩法

朋友们好&#xff01; 春天来了&#xff0c;我们热情洋溢的团队很高兴能为 Gotchiverse 带来一堆新鲜的更新和丰富的功能。让我们一起来看看这次开发更新带来了什么&#xff1a; Gotchichain 选择定居基地 精神力量竞技场获得了 EBIC 更新 高奇守护者通过全新的进阶系统提升…

C# APS.NET CORE 6.0 WebApi在IIS部署报错

今天尝试着把基于 APS.NET CORE6.0开发的webAPI程序部署到IIS中&#xff0c;当打开网站地址时报错&#xff0c;无法打开&#xff0c;于是查找资料最终进行了解决。 打开 IIS →模块 查看列表中是否存在 AspNetCoreModuleV2&#xff0c;如下&#xff1a; 对应的应用池需要选择“…

海外云服务对比: AWS、GCP、Azure 与 DigitalOcean

云计算市场持续增长&#xff0c;预计到2030年将达到 2432.87 亿美元。在这个庞大的市场中&#xff0c;三家云服务提供商——亚马逊&#xff08;AWS&#xff09;、谷歌云平台&#xff08;GCP&#xff09;和微软Azure——共占云市场份额的64%。当用户选择云服务提供商来托管他们的…

【React】反向代理和修改打包后的目录

反向代理 前提是做了反向代理&#xff1a; 安装 http-proxy-middleware npm i http-proxy-middleware在src/下新建 setupProxy.js const proxy require("http-proxy-middleware");module.exports function(app) {app.use(proxy("/api", {target: &qu…

C++ | Leetcode C++题解之第44题通配符匹配

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isMatch(string s, string p) {auto allStars [](const string& str, int left, int right) {for (int i left; i < right; i) {if (str[i] ! *) {return false;}}return true;};auto charMatch []…

时隔5年,MobileNet V4发布!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 距离MobileNet系列上一代版本MobileNet V3的发布已经过去五年 熟悉该系列的同学应该知道&#xff0c;MobileNet致力于维持神经网络在精度和效率之间的微妙平衡&#xff0c;为了让用户在移动设备上拥有…

贪吃蛇撞墙功能的实现 和自动行走刷新地图 -- 第三十天

1.撞墙 1.1最初的头和尾指针要置为空&#xff0c;不然是野指针 1.2 在增加和删除节点后&#xff0c;判断是否撞墙&#xff0c;撞墙则初始话蛇 1.3在撞墙后初始化蛇&#xff0c;如果头不为空就撞墙&#xff0c;得定义临时指针指向头&#xff0c;释放头节点 2.自动刷新地图 2.1…

从Kafka的可靠性设计体验软件设计之美

目录 1. Kafka可靠性概述 2. 副本剖析 2.1 什么是副本 2.2 副本失效场景 2.3 数据丢失场景 2.4 解决数据丢失方案 3. 日志同步机制 4. 可靠性分析 1. Kafka可靠性概述 Kafka 中采用了多副本的机制&#xff0c;这是大多数分布式系统中惯用的手法&#xff0c;以此来实现水平扩…

Web-SpringBootWen

创建项目 后面因为报错&#xff0c;所以我把jdk修改成22&#xff0c;仅供参考。 定义类&#xff0c;创建方法 package com.start.springbootstart.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotati…

java-Spring-(MyBatis框架-xml管理)

目录 前置条件 xml与注解比较 1.1 xml定义 1.2 和SQL注解比较 建包准备 插入数据 ​编辑 更新数据 删除数据 查询数据 查看单字段查询 &#x1f3f7;&#x1f4a3;前置条件 创建一个spring boot 初始化的项目 &#x1f3f7;&#x1f4a3;xml与注解比较 1.1 xml定义 …