数据结构:优先级队列—堆

一、优先级队列

1、优先级队列概念

优先级队列,听名字我们就知道他是一种队列,队列在前面我们已经学习过了,它是一种先进先出的数据结构,但是在特殊的情况下,我们我们队列中元素是带有一定优先级的它需要比我们此时的队头元素,更先的出队列,或者更先的入队列

比如,当我们刚进入游戏的时候,突然有人来敲门,在这种情况下,我们是不是应该先去开门,虽然我们是先进的游戏,但是我们应该先去开门。

或者,当我们要乘坐飞机时,我们是经济舱的乘客,现在我们正在排队检票,下一个就是你,但是这时来了个头等舱的人,他肯定是要比我们先进的。

在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(PriorityQueue)

2、堆的概念

我们在前面了解了完全二叉树的概念,每一层的节点都是从左往右的,依次排列,中间不能空着元素。这就是完全二叉树的概念,那么完全二叉树跟我们今天要讲的堆又有什么关系呢?

其实,堆是一个(特殊的)完全二叉树,每个父节点都不大于或者不小于自己的孩子节点,因此我们将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

层序遍历这个二叉树,顺序的放入一个数组中,像如果有个关键码的集合K={ k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki=K2i+1且Ki>=K2i+2)i=0, 1,2…,则称为小堆(或大堆)

这就是堆的存储。因此从逻辑上来说,堆是一棵完全二叉树,从存储底层来说,堆底层是一个数组。

二、优先级队列的模拟实现

1、堆的存储方式

从上述堆的概念可知,堆是一棵完全二叉树,因此可以层序遍历的规则采用顺序的方式来高效存储

但是我们要注意,如果此时我们不是堆,而是一棵非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低

2、堆的创建

根据上述的概念知道,堆有两种:大根堆和小根堆,但如果此时我们给出一个大小顺序不同的集合,我们该如何创建一个大根堆或小根堆呢?

这就要引出我们的一种调整方法:向下调整

(1)堆向下调整

我们以这个集合{ 27,15,19,18,28,34,65,49,25,37}为例,我们先利用层序遍历的方式画出他的二叉树图。

我们希望将这棵二叉树调整成一个大根堆,但根据上图我们发现,他的最后的根节点和左右两边,并不满足一个大根堆的形式,因此我们需要将他进行调整,我们将最后根节点左右两边最大的一个结点与他进行交换,这样我们的最后根节点和他的左右两边就形成了一个大根堆,而这样一个往下调整的过程我们就称它为向下调整,但是我们发现进行调整之后,我们有的之后的根结点和他的左右两边,他就不是一个大根堆了因此我们需对他进行调整,因此在这里我们就得出了我们向下调整的过程

1.让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)

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

  (1)parent大于较大的孩子child,调整结束

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

 public void createHeap(){
//我们从下往上走 ,找倒数第⼀个⾮叶⼦节点,从该节点位置开始往前⼀直到根节点,遇到⼀个节点,应⽤向下调整for(int parent = (usesize-1-1)/2;parent >=0 ;parent--){sifDown(parent,usesize);}}public void sifDown(int parent,int usesize){// child先标记parent的左孩⼦,因为parent可能右左没有右int child = 2*parent + 1;while(child < usesize){// 如果右孩⼦存在,找到左右孩⼦中较⼩的孩⼦,⽤child进⾏标记if(child+1 < usesize && elem[child] < elem[child+1]){child++;}// 如果双亲⽐其最⼩的孩⼦还大,说明该结构已经不满⾜堆的特性了,将双亲与较⼩的孩⼦交换if(elem[child] > elem[parent]){swap(child,parent);// parent中⼤的元素往下移动,可能会造成⼦树不满⾜堆的性质,因此需要继续向下调整parent = child;child = 2*parent + 1;}else{break;}}}

(2)堆向上调整:堆的插入

堆的插入总共需要两个步骤:

1. 先将元素放到堆的最后(注意:空间不够时需要扩容)

2. 将最后新插入的节点向上调整,直到满足堆的性质

在这里我们引入了一个新的方法:向上调整,他其实跟我们的向下调整是一样的只是向下调整是传入父亲结点,再去求孩子结点进行判断,而向上调整是传入孩子结点,求父亲结点进行判断

 public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,2*elem.length);}elem[usesize] = val;sifUp(usesize);usesize++;}public  void sifUp(int child){int parent = (child-1)/2;while(parent >=0){if(elem[child] > elem[parent]){swap(child,parent);child = parent;parent =(child-1)/2;}else{break;}}}

(3)堆的删除

注意:堆的删除我们要删除的是堆顶元素。

堆的删除总共需要三个步骤:

1. 将堆顶元素对堆中最后一个元素交换

2. 将堆中有效数据个数减少一个

3. 对堆顶元素进行向下调整

 public  int poll(int val){if(empty()){return -1;}int oldVale = elem[0];swap(0,usesize-1);usesize--;sifDown(0,usesize);return oldVale;}

完整代码

public class TestHeap {public int[] elem;public int usesize;public TestHeap(){elem = new int[10];}public void len(int[] arr){for (int i = 0; i < arr.length; i++) {elem[i] = arr[i];usesize++;}}public void createHeap(){for(int parent = (usesize-1-1)/2;parent >=0 ;parent--){sifDown(parent,usesize);}}public void sifDown(int parent,int usesize){int child = 2*parent + 1;while(child < usesize){if(child+1 < usesize && elem[child] < elem[child+1]){child++;}if(elem[child] > elem[parent]){swap(child,parent);parent = child;child = 2*parent + 1;}else{break;}}}public void swap(int i,int j){int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,2*elem.length);}elem[usesize] = val;sifUp(usesize);usesize++;}public  void sifUp(int child){int parent = (child-1)/2;while(parent >=0){if(elem[child] > elem[parent]){swap(child,parent);child = parent;parent =(child-1)/2;}else{break;}}}public  int poll(int val){if(empty()){return -1;}int oldVale = elem[0];swap(0,usesize-1);usesize--;sifDown(0,usesize);return oldVale;}public boolean isFull(){return elem.length == usesize;}public  boolean empty(){return usesize == 0;}public int peekHeap(){return elem[0];}}

好了今天的分享就到这里了,还请大家多多关注,我们下一篇见!

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

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

相关文章

.Net Web API 访问权限限定

看到一个代码是这样的&#xff1a; c# webapi 上 [Route("api/admin/file-service"), AuthorizeAdmin] AuthorizeAdmin 的定义是这样的 public class AuthorizeAdminAttribute : AuthorizeAttribute {public AuthorizeAdminAttribute(){Roles "admin"…

什么情况下,C#需要手动进行资源分配和释放?什么又是非托管资源?

扩展&#xff1a;如何使用C#的using语句释放资源&#xff1f;什么是IDisposable接口&#xff1f;与垃圾回收有什么关系&#xff1f;-CSDN博客 托管资源的回收有GC自动触发&#xff0c;而非托管资源需要手动释放。 在 C# 中&#xff0c;非托管资源是指那些不由 CLR&#xff08;…

【人工智能】基于Python的机器翻译系统,从RNN到Transformer的演进与实现

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 机器翻译(Machine Translation, MT)作为自然语言处理领域的重要应用之一,近年来受到了广泛的关注。在本篇文章中,我们将详细探讨如何使…

2025年2月2日(网络编程 tcp)

tcp 循环服务 import socketdef main():# 创建 socket# 绑定tcp_server socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server.bind(("", 8080))# socket 转变为被动tcp_server.listen(128)while True:# 产生专门为链接进来的客户端服务的 socketprint(&qu…

像接口契约文档 这种工件,在需求 分析 设计 工作流里面 属于哪一个工作流

οゞ浪漫心情ゞο(20***328) 2016/2/18 10:26:47 请教一下&#xff0c;像接口契约文档 这种工件&#xff0c;在需求 分析 设计 工作流里面 属于哪一个工作流&#xff1f; 潘加宇(35***47) 17:17:28 你这相当于问用例图、序列图属于哪个工作流&#xff0c;看内容。 如果你的&quo…

Zabbix 推送告警 消息模板 美化(钉钉Webhook机器人、邮件)

目前网络上已经有很多关于Zabbix如何推送告警信息到钉钉机器人、到邮件等文章。 但是在搜索下来&#xff0c;发现缺少了对告警信息的美化的文章。 本文不赘述如何对Zabbix对接钉钉、对接邮件&#xff0c;仅介绍我采用的美化消息模板的内容。 活用AI工具可以减轻很多学习、脑力负…

Node.js 的底层原理

Node.js 的底层原理 1. 事件驱动和非阻塞 I/O Node.js 基于 Chrome V8 引擎&#xff0c;使用 JavaScript 作为开发语言。它采用事件驱动和非阻塞 I/O 模型&#xff0c;使其轻量且高效。通过 libuv 库实现跨平台的异步 I/O&#xff0c;包括文件操作、网络请求等。 2. 单线程事…

实现C语言的原子操作

什么是原子操作呢&#xff1f;即操作本身无法再被划分为更细的步骤。我们一般都是在多线程环境中&#xff0c;才会需要原子操作的支持。因为当多个线程中对共享资源进行原子操作时&#xff0c;编译器和 CPU 将能够保证这些操作的正确执行。原子操作就是说同一时刻只会有一个线程…

何谓共赢?

A和B是人或组织&#xff0c;他们怎样的合作才是共赢呢&#xff1f; 形态1:A提供自己的身份证等个人信息&#xff0c;B用来作贷款等一些事务&#xff0c;A每月得到一笔钱。 A的风险远大于收益&#xff0c;或者B从事的是非法行为&#xff1b; 形态2:A单方面提前终止了与B的合作…

物联网 STM32【源代码形式-使用以太网】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】

物联网&#xff08;IoT&#xff09;‌是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器等装置与技术&#xff0c;实时采集并连接任何需要监控、连接、互动的物体或过程&#xff0c;实现对物品和过程的智能化感知、识别和管理。物联网的核心功能包括数据采集与监…

Redis|前言

文章目录 什么是 Redis&#xff1f;Redis 主流功能与应用 什么是 Redis&#xff1f; Redis&#xff0c;Remote Dictionary Server&#xff08;远程字典服务器&#xff09;。Redis 是完全开源的&#xff0c;使用 ANSIC 语言编写&#xff0c;遵守 BSD 协议&#xff0c;是一个高性…

WebForms DataList 深入解析

WebForms DataList 深入解析 引言 在Web开发领域,控件是构建用户界面(UI)的核心组件。ASP.NET WebForms框架提供了丰富的控件,其中DataList控件是一个灵活且强大的数据绑定控件。本文将深入探讨WebForms DataList控件的功能、用法以及在实际开发中的应用。 DataList控件…

深入理解Java中的String

前言 在Java中&#xff0c;String类是一个非常重要的内置类&#xff0c;用于处理字符串数据。字符串是不可变的&#xff08;immutable&#xff09;&#xff0c;这意味着一旦创建&#xff0c;字符串的内容不能被修改。作为Java中最为基础和常用的类之一&#xff0c;String类在内…

基于人脸识别的课堂考勤系统

该项目是一个基于人脸识别的课堂考勤系统&#xff0c;使用Python开发&#xff0c;结合了多种技术实现考勤功能。要开发类似的基于人脸识别的考勤系统&#xff0c;可参考以下步骤&#xff1a; 环境搭建&#xff1a;利用Anaconda创建虚拟环境&#xff0c;指定Python版本为3.8&am…

Ubuntu安装GitLab

在 Ubuntu 上安装 GitLab 的步骤如下。这里以 GitLab Community Edition&#xff08;CE&#xff09;为例&#xff1a; 前提条件 确保你的 Ubuntu 系统是 20.04 或更高版本。确保你的系统满足 GitLab 的硬件要求。 步骤 更新系统包&#xff1a; sudo apt update sudo apt upg…

Vue.js 的介绍与组件开发初步

Vue.js 的介绍与组件开发初步 Vue.js 的介绍与组件开发初步引言第一部分&#xff1a;Vue.js 基础入门1.1 什么是 Vue.js&#xff1f;1.2 搭建 Vue.js 开发环境安装 Node.js 和 npm安装 Vue CLI创建新项目运行示例 1.3 第一个 Vue.js 示例 第二部分&#xff1a;Vue.js 组件开发基…

架构技能(四):需求分析

需求分析&#xff0c;即分析需求&#xff0c;分析软件用户需要解决的问题。 需求分析的下一环节是软件的整体架构设计&#xff0c;需求是输入&#xff0c;架构是输出&#xff0c;需求决定了架构。 决定架构的是软件的所有需求吗&#xff1f;肯定不是&#xff0c;真正决定架构…

Linux:线程池和单例模式

一、普通线程池 1.1 线程池概念 线程池&#xff1a;一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价&…

Leetcode598:区间加法 II

题目描述&#xff1a; 给你一个 m x n 的矩阵 M 和一个操作数组 op 。矩阵初始化时所有的单元格都为 0 。ops[i] [ai, bi] 意味着当所有的 0 < x < ai 和 0 < y < bi 时&#xff0c; M[x][y] 应该加 1。 在 执行完所有操作后 &#xff0c;计算并返回 矩阵中最大…

C++泛型编程指南04-(对默认调用参数的类型推断)

文章目录 问题描述解决方案示例代码 关键点解释进一步改进&#xff1a;结合概念约束 你提到的情况确实是一个常见的问题&#xff1a;在C中&#xff0c;类型推断不适用于默认调用参数。这意味着如果你希望函数模板能够通过默认参数来实例化&#xff0c;你需要为模板参数提供一个…