优先级队列(堆)(1)

目录

一. 优先级队列

1.1 概念

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

2.1 堆的概念

2.2 堆的存储方式

2.3 堆的创建

2.3.1 堆向下调整

2.3.2 堆的创建

2.3.3 建堆的时间复杂度

2.4 堆的插入与删除

2.4.1 堆的插入

2.4.2 堆的删除

 常见习题:


一. 优先级队列

1.1 概念

前面介绍过队列, 队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列 ,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。
在这种情况下, 数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue)

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

JDK1.8 中的 PriorityQueue 底层使用了堆这种数据结构 ,而堆实际就是在完全二叉树的基础上进行了一些调整。

2.1 堆的概念

如果有一个 关键码的集合 K = {k0 k1 k2 kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储在一 个一维数组中 ,并满足: Ki <= K2i+1 Ki<= K2i+2 (Ki >= K2i+1 Ki >= K2i+2) i = 0 1 2… ,则 称为小堆 ( 或大堆) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
堆的性质:
  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

2.2 堆的存储方式

从堆的概念可知, 堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储
注意:对于 非完全二叉树,则不适合使用顺序方式进行存储 ,因为为了能够还原二叉树, 空间中必须要存储空节 点,就会导致空间利用率比较低
将元素存储到数组中后,可以根据二叉树的性质 对树进行还原。假设 i 为节点在数组中的下标,则有:
如果 i 0 ,则 i 表示的节点为根节点,否则 i 节点的双亲节点为 (i - 1)/2
如果 2 * i + 1 小于节点个数,则节点 i 的左孩子下标为 2 * i + 1 ,否则没有左孩子
如果 2 * i + 2 小于节点个数,则节点 i 的右孩子下标为 2 * i + 2 ,否则没有右孩子

2.3 堆的创建

2.3.1 堆向下调整

对于集合 { 27,15,19,18,28,34,65,49,25,37 } 中的数据,先将其创建成一棵树, 观察是否满足堆的性质
仔细观察上图后发现: 根节点的左右子树已经完全满足小根堆的性质,因此只需将根节点向下调整好即可
向下过程 ( 以小根堆为例 )
1. parent标记需要调整的节点,child标记parent的左孩子 ( 注意: parent 如果有孩子一定先是有左孩子 )
2. 如果 parent 的左孩子存在,即 :child < size , 进行以下操作,直到 parent 的左孩子不存在
  • parent右孩子是否存在,存在找到左右孩子中最小的孩子,用child进行标记
  • parent与较小的孩子child比较,如果: parent小于较小的孩子child,调整结束 ,  否则:交换parent与较小的孩子child,交换完成之后,可能导致下面的子树不满足堆的性质,因此需要继续向下调整,即parent = childchild = parent*2+1; 然后继续2步骤

参考代码:

    //向下调整(小根堆)
    private void siftDownSmallHeap(int parent ,int end){
        int child = 2 * parent + 1;//child==左孩子
        while(child < end){
            //如果有右孩子并且右孩子是左右孩子中最小的
            if(child + 1 < usedSize && elem[child] > elem[child + 1]){
                child++;//child==右孩子
            }
            //如果孩子小于父母, 不满足小根堆的性质, 则交换
            if(elem[parent] > elem[child]){
                swap(child,parent);//交换
                parent = child;//向下调整
                child = 2*parent+1;//向下调整
            }else{
                break;
            }
        }
    }
    //向下调整(大根堆)
    private void siftDownBigHeap(int parent ,int end){
        int child = 2 * parent + 1;//child==左孩子
        while(child < end){
            //如果有右孩子并且右孩子是左右孩子中最大的
            if(child + 1 < usedSize && elem[child] > elem[child + 1]){
                child++;//child==右孩子
            }
            //如果孩子大于父母, 不满足大根堆的性质, 则交换
            if(elem[parent] > elem[child]){
                swap(child,parent);//交换
                parent = child;//向下调整
                child = 2*parent+1;//向下调整
            }else{
                break;
            }
        }
    }
时间复杂度分析:
最坏的情况 即图示的情况, 从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(log_{2}n)

 

2.3.2 堆的创建

注意:

在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整,  不然会有增加许多不必要的调整,   因此在创建堆的时候,  需要从最后一个结点开始调整

对于普通的序列 { 1,5,3,8,7,6 }, 假设要创建大根堆
第一步: 右树,  3 < 6 , 交换3 6
第二步: 左树, 7 8 最大值8 > 5 , 交换5 8
第三步:根, 6 8 最大值8 > 1, 交换8 1
第三步完成后, 破坏了左子树的大根堆结构, 如图所示:
第四步: 所以接下来就是向下调整时需要做的工作, 即重新对阴影部分进行构造堆, 递归的进行向下调整
第五步:完成

 参考代码:

/*
最后一个结点:usedSize-1
最后一个结点的父亲: (usedSize-1-1)/2
 */
    public void createBigHeap(){
        for(int parent = (usedSize-1-1)/2;parent >=0;parent--){//从最后一个结点的父亲结点开始判断是否需要调整
            siftDownBigHeap(parent,usedSize);//调用大根堆对应的向下调整方法

        }
    }

2.3.3 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明 ( 时间复杂度本来看的就是近似值,多几个节点不影响最终结果)
因此: 建堆的时间复杂度为 O(N)

2.4 堆的插入与删除

2.4.1 堆的插入

堆的插入总共需要两个步骤:
1. 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
2. 将最后新插入的节点向上调整,直到满足堆的性质
参考代码:
   //插入元素
    public void offer(int val){
        //判断是否满, 满则扩容
        if(isFull()){
            this.elem = Arrays.copyOf(elem,elem.length*2);
        }
        //插入元素到最后
        elem[usedSize] = val;
        usedSize++;
        //向上调整
        siftUp(usedSize-1);
    }
    //向上调整(大根堆)
    private void siftUp(int child){
        int parent = (child-1)/2;
        while(child > 0){
            if(elem[child] > elem[parent]){
                swap(child,parent);
                child = parent;
                parent = (child-1)/2;
            }else{
                break;
            }
        }
    }
    
    private boolean isFull(){
        return elem.length == usedSize;
    }

2.4.2 堆的删除

注意:堆的删除一定删除的是堆顶元素。
具体如下:
1. 将堆顶元素对堆中最后一个元素交换
2. 将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整

参考代码:

  public int poll(){
        int tmp = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDownBigHeap(0,usedSize);
        return tmp;
    }

 常见习题:

1. 下列关键字序列为堆的是 :(A)
A: 100,60,70,50,32,65 B: 60,70,65,50,32,100 C: 65,100,70,32,50,60
D: 70,65,100,32,50,60 E: 32,50,100,70,65,60 F: 50,100,70,65,60,32
解:堆要满足大根堆或小根堆
A: 满足大根堆,选A
2. 已知小根堆为 8,15,10,21,34,16,12 ,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是 (C)
A: 1 B: 2 C: 3 D: 4
解:
小根堆   删除后:
比较 选C
3. 最小堆 [0,3,2,5,7,4,6,8], 在删除堆顶元素 0 之后,其结果是 (C)
A: [3 2 5 7 4 6 8] B: [2 3 5 7 4 6 8]
C: [2 3 4 5 7 8 6] D: [2 3 4 5 6 7 8]
解:
小根堆 删除后:
向下调整: 选C

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣:数组篇

1、数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合。 需要两点注意的是 数组下标都是从0开始的。数组内存空间的地址是连续的 因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址。 …

【基础CSS】

本文章属于学习笔记&#xff0c;在https://www.freecodecamp.org/chinese/learn/2022/responsive-web-design/中练习 二、 CSS 样式&#xff0c;新建一个文件.css&#xff0c;该文件不含有style标签 <style>. h1&#xff0c;h2&#xff0c;p{ text-align&#xff1a;ce…

03-自媒体文章发布-黑马头条

自媒体文章发布 1)自媒体前后端搭建 1.1)后台搭建 ①&#xff1a;资料中找到heima-leadnews-wemedia.zip解压 拷贝到heima-leadnews-service工程下&#xff0c;并指定子模块 执行leadnews-wemedia.sql脚本 添加对应的nacos配置 spring:datasource:driver-class-name: com…

Linux:导出环境变量命令export

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 Linux中的内建命令export命令用于创建一个环境变量&#xff0c;或将一个普通变量导出为环境变量&#xff0c;并且在这个过程中&#xff0c;可以给该环境变量赋值。 下面…

Java后端八股文之java基础

文章目录 0.Java 中有 8 种基本数据类型1. 为什么浮点数运算会丢失精度&#xff1f;如何解决&#xff1f;2. 面向对象的三大特征2.1 封装2.2 继承2.3 多态 3. 深拷贝和浅拷贝的区别&#xff1f;什么是引用拷贝&#xff1f;4. equals方法与“”方法4.1 4.2 equals方法 5.hashcod…

计算机组成原理实验报告1 | 实验1.1 运算器实验(键盘方式)

本文整理自博主大学本科《计算机组成原理》课程自己完成的实验报告。 —— *实验环境为学校机房实验箱。 目录 一、实验目的 二、实验内容 三、实验步骤及实验结果 Ⅰ、单片机键盘操作方式实验 1、实验连线&#xff08;键盘实验&#xff09; 2、实验过程 四、实验结果的…

TortoiseSVN 报错:The server unexpectedly closed the connetion

前言 CentOS7Linux 安装subversionmod_dav_svn&#xff0c;搭建subversion(svn)服务器 The server unexpectedly closed the connetion 解决办法 重启Apache服务 shell> systemctl restart httpd

12 list的使用

文档介绍 文档介绍 1.list是可以在常数范围内的任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代 2.list的底层是带头双向链表循环结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和…

【JavaScript】数据类型转换 ① ( 隐式转换 和 显式转换 | 常用的 数据类型转换 | 转为 字符串类型 方法 )

文章目录 一、 JavaScript 数据类型转换1、数据类型转换2、隐式转换 和 显式转换3、常用的 数据类型转换4、转为 字符串类型 方法 一、 JavaScript 数据类型转换 1、数据类型转换 在 网页端 使用 HTML 表单 和 浏览器输入框 prompt 函数 , 接收的数据 是 字符串类型 变量 , 该…

docker容器镜像管理+compose容器编排(持续更新中)

目录 一、 Docker的基本组成 二、 容器和镜像的关系 2.1 面向对象角度 2.2 从镜像容器角度 三、 容器命令 3.1 使用Ubuntu 3.1.1 下载镜像 3.1.2 新建和启动容器 run 3.1.3交互式 compose编排与部署 1. docker-compose部署 2. docker-compose.yml模板 …

社区维修平台|基于SpringBoot+ Mysql+Java+JSP技术的社区维修平台设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 住户后台功能 维修员前台功能 维修员后台功能 管理员功能登录 系统功能设计 数据库E…

数据结构:哈希表

1.散列表的概念: 根据要存储的数据记录的关键字值计算出应该存储的位置 基本思想:记录的存储位置与关键字之间存在对应关系 Loc(i)H(keyi)-----等号右边就称之为hash函数.等号左边就是对应的存储位置; 2.哈希表的优缺点 这个就是散列表的特点:查找效率高,空间利用率低;&am…

java-双列集合

什么是双列集合&#xff1f; 集合中每次存的数据是成对存入的 以及它的特点是什么&#xff1f; 特别注意&#xff1a;键不可重复&#xff0c;值可以 Map是双列集合的顶层接口 Map 它有哪些方法呢&#xff1f; Map的常用API 添加 添加操作的代码如下 我们要明白一些细节&…

ChatGPT浪潮来袭!谁先掌握,谁将领先!

任正非在接受采访时说 今后职场上只有两种人&#xff0c; 一种是熟练使用AI的人&#xff0c; 另一种是创造AI工具的人。 虽然这个现实听起来有些夸张的残酷&#xff0c; 但这就是我们必须面对的事实 &#x1f4c6; 对于我们普通人来说&#xff0c;我们需要努力成为能够掌握…

uniapp开发的跳转到小程序

uniapp开发的h5跳转到小程序 https://www.cnblogs.com/xiaojianwei/p/16352698.html官方&#xff1a;使用 URL Scheme 打开小程序 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html 链接代码 <a href"weixin://dl/business/…

AHU 数据库 实验三

《数据库》实验报告 【实验名称】 实验3 数据库的连接查询 【实验目的】 1. 熟悉基本的连接查询的概念和作用&#xff1b; 2. 了解数据库管理系统DBMS 实现连接查询的基本方法&#xff1b; 3. 掌握SQL语言连接查询语句的语法和功能&#…

备战python蓝桥杯1.0

1.输入输出 1.1输入一行 字符组 # 输入一个字符串&#xff0c;分割成单个字符存到列表a a[i for i input()]1.2输入一行 一组数 #输入一组数&#xff0c;赋值给列表a alist(map(int,input().split()))1.3输入多行 字符串 #先输入n&#xff0c;再输入n行的字符串&#xff0c;…

算法提高之楼兰图腾(树状数组)

楼兰图腾(树状数组) 核心算法&#xff1a;树状数组 将下标转化为二进制 例如11100100 父节点下标x 子节点下标i 由下图可知 每一个数都可以由其子节点**(如果有)**求和得到**由父节点找子节点&#xff1a;**每个子节点下标 –> x – 1 – lowbit(x – 1)由子节点找父节点&am…

前端跨页面通信的几种方式---同源

参考链接 1、LocalStorage:当 LocalStorage 变化时&#xff0c;会触发storage事件。利用这个特性&#xff0c;我们可以在发送消息时&#xff0c;把消息写入到某个 LocalStorage 中&#xff1b;然后在各个页面内&#xff0c;通过监听storage事件即可收到通知。 2、BroadCast C…

SpringBoot+Vue项目报错(问题已解决)

1、错误日志 2、分析原因&#xff1a; JWT strings must contain exactly 2 period characters. Found: 0 JWT字符串必须包含2个句号字符。发现:0 分析&#xff1a;可以判断出大概可能是token格式出现了问题 3、参考 http://t.csdnimg.cn/hfEiY 4、检查后端代码是否出现问…