数据结构:优先级队列(堆)

概念

优先级队列是啥? 

队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列。
在这种情况下, 数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue)

堆是啥?

优先级队列的底层运用到堆这种数据结构

堆的特点:总是一棵完全二叉树

大根堆:

每一棵树的根结点总是比左右子节点大

小根堆:

每一棵树的根结点的值总是比左右子节点小,不考虑左右子节点谁大谁小

堆的存储

存储方式采用层序遍历的方式把二叉树的元素一一放到数组里面

那数组可不可以存储非完全二叉树呢?答案是可以的,但是会有空间浪费的情况

像上面右边图,4的位置没有存储元素,这就是一种空间浪费

来手搓一个堆

回顾一下二叉树里面性质的第五点

如何将普通数组转换成堆 

把下面数组的元素画成堆

先画成一棵普通的二叉树

画成大根堆

28<37,互换。最左边子树49>25>18,把49变成该树的根结点,最右边的树65>34>19,也进行交换

调整第二层的树,49>15>37,49作为根,而15<18<25,下方的树得把25变成根

最上面一层的树,65>49>27,65做根结点,而27<34,所以34还得作为该子树的根结点

这就是一个大根堆了

总结:

1.从最后一棵子树开始调整

2.在要变换的树里面,从左右孩子里面找到最大的与根结点比较,大了就进行互换

3.如果能够知道子树根结点下标,那么下一棵子树就是当前根结点下标-1

4.一直调整到0下标这个树为止

先写个初步的代码

public class TestHeap {private 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++;}}

问题

1.最后一棵子树根结点下标是多少

因为i = len-1,所以根结点index = (i-1)/2

    public void createHeap(){//usedSize-1相当于最后一棵树孩子结点的下标i,再-1是为了求父结点for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {siftDown(parent,usedSize);}}

2.每棵子树调整完之后,结束的位置怎么定?也就是我要从哪里开始调整下一棵子树? 

我们采用向下调整的方法(注意,虽然我们是从最后一颗树往根结点方向调整,但是每一棵树的处理我们还是采用从父结点到子节点的调整方法。为什么用不向上调整?后面我会说到。)

找到最后一个元素置为c,其根结点为p

调整完后不知道下面还有没有元素要调整,所以c还得往下走 

此时c的坐标是19 > 10了,所以可以停止了

    private void siftDown(int parent, int len){int child = 2 * parent + 1;while(child < len){//左右孩子比较大小if(elem[child] < elem[child + 1]){child = child + 1;}//走完上面的if,证明child下标一定是左右两个孩子最大值的下标}}

现在问题来了,写到这里会发生数组越界,如果我的child移到9下标这里,那这个if判断elem[child] < elem[child+1] 这里的child+1 = 10 = usedSize,而这棵树根本就没有10这个下标,造成了越界 

修改一下代码

            if(child+1<len && elem[child] < elem[child + 1]){child = child + 1;}

后面就是比较孩子和父结点的代码了

   /*** 向下调整* @param parent* @param len*/private void siftDown(int parent, int len){int child = 2 * parent + 1;while(child < len){//左右孩子比较大小if(child+1<len && elem[child] < elem[child + 1]){child = child + 1;}//走完上面的if,证明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;//不用比不用调了}}}

测试一下,没有问题😊

怎么计算这个堆的时间复杂度?

考虑最坏情况,就是满二叉树的情况

首先明确一点,最后一层结点时不进行调整的,一般是从倒数第二层结点开始调整的

设树的高度是h

T(N) = (h-1)*2^0+(h-2)*2^1+(h-3)*2^2+......+2*2^(h-3)+1*2^(h-2)

怎么求这个等式?采用错位相减

根据等比求和公式

T(n) = 2 ^ h - 1 - h

因为n=2^h-1    --> h = log(n+1)

代进去T(n) = n - log(n+1)

因为log(n+1)的图长这样,n越大越趋于一个常数

所以整个等式占支配地位的还得是n,所以T(N) ≈ n  -->时间复杂度:O(N)


堆的插入

如果插入的数值比较小

如果插入的数值比较大,那就得一层一层进行调整

这种调整叫做向上调整

    public void swap(int i, int j){int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}public void push(int val){if(isFull()){elem = Arrays.copyOf(elem, 2*elem.length);}elem[usedSize] = val;//向上调整siftUp(usedSize);usedSize++;}//判断满不满public boolean isFull(){return usedSize == elem.length;}public 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;}}}

在测试里面把80push进去,没有问题😊

堆的插入的时间复杂度

因为最坏情况插入的元素是最大的,那这个元素最多也就向上调整到根节点的位置,也就是h

复杂度就是O(logN) 

欸那为什么不用向上调整来建堆呢?😐

我们分析一下,拿这棵满二叉树来说,最底层有8个元素,已经占了一半了,网上建堆得每个元素都遍历一遍,时间复杂度太大了

 

 

堆的删除

因为堆的删除一定是删除优先级最高的值,所以一定是删除大根堆的根结点

比如这个,我们要做的就是删除65

第一步:把65(0下标)与28(最后一个元素)进行交换

第二步:向下调整0下标

    public int pop(){if(empty()){throw new EmptyException("数组空了!");}int oldVal = elem[0];swap(0,usedSize-1);usedSize--;siftDown(0,usedSize);return oldVal;}public boolean empty(){return usedSize == 0;}

测试一下,没有问题😊

习题:

选A(可以自己画图,反正就是层序遍历画树)

选C

 

总共比较3次,左边那个15的原本就是小根堆,所以就不用比较

选C

PriorityQueue

Java集合框架提供了PriorityQueue的优先级队列

注意事项:

        PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>();priorityQueue1.offer(new Student("zhangsan",10));priorityQueue1.offer(new Student("lisi",12));

 1.PriorityQueue放入的元素必须能比较大小,否则会报出下面的错误 

2.不能插入null对象,否则会报出下面的错误

        PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>();priorityQueue1.offer(null);

 

3.没有容量限制,可以插入任意多个元素,内部会自动扩容

4.插入和删除都是O(logn) 

5.使用了最小堆的数据结构,所以每次获取的元素都是最小的元素

oj练习

面试题 17.14. 最小K个数 - 力扣(LeetCode)

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]

提示:

  • 0 <= len(arr) <= 100000
  • 0 <= k <= min(100000, len(arr))

方法一:

建立最小堆,把堆顶k个元素输出出来就行了

代码

    public int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();//向上调整 O(logN)for (int i = 0; i < array.length; i++) {priorityQueue.offer(array[i]);}int[] ret = new int[k];//k*logNfor (int i = 0; i < k; i++) {ret[i] = priorityQueue.poll();}return ret;}

虽然通过了,但是时间复杂度有点大 

方法二:

1.建立大根堆,大小为k,比如我们可以拿前三个元素来建一个大根堆

2.从第k+1个元素开始比较,如果比堆顶元素小,则入堆。当前的堆顶元素(较大的)就舍弃掉,因为已经不符合我对前k个最小的元素的要求了

遍历完整个大根堆长这样

问题来了,PriorityQueue是默认采用小根堆的底层,那我们要怎么让它采用大根堆呢

PriorityQueue源码里面的有一个compare函数

这个函数外层是compareTo函数

这两个函数结合一下,把小的放在前面,大的放在后面,所以实现了小根堆的底层

我们可以重写PriorityQueue里面的compare函数,把大的放在前面

class Imp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}
}

整个的代码(上面的重写可以扔到匿名内部类里面)

    public static int[] smallestK(int[] array, int k) {int[] ret = new int[k];if(array == null || k <= 0) {return ret;}//匿名内部类PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});//1、建立大小为k的大根堆 O(K*logK)for (int i = 0; i < k; i++) {priorityQueue.offer(array[i]);}//2、遍历剩下的元素 (N-K)*logK// (K*logK) + N*logK  - K*logK   =   N*logK  -->时间复杂度for (int i = k; i < array.length; i++) {int top = priorityQueue.peek();//27if(array[i] < top) {priorityQueue.poll();priorityQueue.offer(array[i]);}}//下面这个不能算topK的复杂度 这个地方是整理数据//k*logKfor (int i = 0; i < k; i++) {ret[i] = priorityQueue.poll();}return ret;}

 

别看力扣上面的通过时间,我们要自行分析时间复杂度 


堆排序

把这个数组从小到大排序,需要建立大根堆

再把这棵树放到堆底,这样最大的元素就有序了

再按照大根堆进行排序(已经有序的元素就不管了),把最大元素49放到堆顶,然后再和堆第的15交换

以此类推,设置一个堆底end,每次拿0下标的元素和它交换,交换完end--

    public void heapSort(){int end = usedSize-1;while(end>0){swap(0,end);siftDown(0,end);end--;}}

时间复杂度O(N*logN)

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

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

相关文章

converted from warning

converted from warning 关注微信&#xff1a;生信小博士 本地或者其它服务器跑同样的代码是正常的&#xff0c;只是有警告&#xff0c;但是在西柚云服务器上面运行会报错&#xff1f; 这是由于您两个环境使用的包版本不一样导致的&#xff0c;有如下解决方法 或者之前只是告警…

Jetpack Compose | State状态管理及界面刷新

我们知道Jetpack Compose&#xff08;以下简称Compose&#xff09;中的 UI 可组合项是通过Composable 声明的函数来描述的&#xff0c;如&#xff1a; Composable fun Greeting() {Text(text "init",color Color.Red,modifier Modifier.fillMaxWidth()) }上面的代…

MySQL实战1

文章目录 主要内容一.墨西哥和美国第三高峰1.准备工作代码如下&#xff08;示例&#xff09;: 2.目标3.实现代码如下&#xff08;示例&#xff09;: 4.相似例子代码如下&#xff08;示例&#xff09;: 二.用latest_event查找当前打开的页数1.准备工作代码如下&#xff08;示例&…

C++设计模式_20_Composite 组合模式

Composite 组合模式和后面谈到的Iterator&#xff0c;Chain of Resposibility都属于“数据结构”模式。Composite 组合模式核心是通过多态的递归调用解耦内部和外部的依赖关系。 文章目录 1. “数据结构”模式1.1 典型模式 2. 动机( Motivation )3. 模式定义4. Composite 组合模…

科普|电源自动测试系统测试的项目都有哪些?

电源自动测试系统是一种用于电源性能自动测试的集成系统&#xff0c;它可以自动检测电源模块或开关电源的输入、输出、保护等各个方面。该系统通常由数据软件和各类硬件测试仪器共同组成&#xff0c;利用通讯总线、测试夹具以及其它线缆等将仪器进行连接组成整体的系统结构&…

day14_集合

今日内容 零、 复习昨日 一、集合框架体系 二、Collection 三、泛型 四、迭代 五、List(ArrayList、LinkedList) 零、 复习 throw和throws什么区别 throwthrows位置方法里面方法签名上怎么写throw 异常对象throws异常类名(多个)作用真正抛出异常对象声明抛出的异常类型 运行时…

成本预算管理系统

成本预算管理系统 功能介绍&#xff1a; 一 基本信息&#xff1a; 1、产品设置&#xff1a;产品的长、宽、高及面积计算公式的设置。 2、板材设置&#xff1a;板材类别、厚度、尺寸的设置 3、系统名称&#xff1a;风管系统的类别设置 4、公司信息&#xff1a;本公司的信息…

【多线程】线程互斥 {竞态条件,互斥锁的基本用法,pthread_mutex系列函数,互斥锁的原理;死锁;可重入函数和线程安全}

一、进程线程间通信的相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。确切的说&#xff0c;临界资源在同一时刻只能被一个执行流访问。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;通过互…

基于鸟群算法的无人机航迹规划-附代码

基于鸟群算法的无人机航迹规划 文章目录 基于鸟群算法的无人机航迹规划1.鸟群搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用鸟群算法来优化无人机航迹规划。 1.鸟群搜索算法 …

0基础学习PyFlink——用户自定义函数之UDF

大纲 标量函数入参并非表中一行&#xff08;Row&#xff09;入参是表中一行&#xff08;Row&#xff09;alias PyFlink中关于用户定义方法有&#xff1a; UDF&#xff1a;用户自定义函数。UDTF&#xff1a;用户自定义表值函数。UDAF&#xff1a;用户自定义聚合函数。UDTAF&…

vue2+ant-design-vue a-select组件二次封装(支持单选/多选添加全选/分页(多选跨页选中)/自定义label)

一、效果图 二、参数配置 1、代码示例 <t-antd-selectv-model"selectVlaue":optionSource"stepList"change"selectChange" />2、配置参数&#xff08;Attributes&#xff09;继承 a-select Attributes 参数说明类型默认值v-model绑定值…

vivado crash

将增量编译去了

FPGA时序分析与约束(9)——主时钟约束

一、时序约束 时序引擎能够正确分析4种时序路径的前提是&#xff0c;用户已经进行了正确的时序约束。时序约束本质上就是告知时序引擎一些进行时序分析所必要的信息&#xff0c;这些信息只能由用户主动告知&#xff0c;时序引擎对有些信息可以自动推断&#xff0c;但是推断得到…

Sprint Cloud Stream整合RocketMq和websocket实现消息发布订阅

1.引入RocketMQ依赖&#xff1a;首先&#xff0c;在pom.xml文件中添加RocketMQ的依赖&#xff1a; <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.0</versi…

文件改名,轻松添加前缀顺序编号,文件改名更高效!

您是否曾经需要批量修改文件名&#xff0c;并希望在文件名中添加特定的前缀或顺序编号&#xff1f;现在&#xff0c;我们为您带来了一款全新的文件改名工具&#xff0c;帮助您轻松解决这个问题&#xff01; 第一步&#xff0c;进入文件批量改名高手主页面&#xff0c;在板块栏…

C++学习笔记之四(标准库、标准模板库、vector类)

C 1、C标准库2、C标准模板库2.1、vector2.1.1、vector与array2.1.2、vector与函数对象2.1.3、vector与迭代器2.1.4、vector与算法 1、C标准库 C C C标准库指的是标准程序库( S t a n d a r d Standard Standard L i b a r a y Libaray Libaray)&#xff0c;它定义了十个大类…

亚马逊,速卖通,美客多如何打造爆款商品,排名提升榜首

1、产品Listing的完整性 Listing是亚马逊A9算法认识你产品的基础&#xff0c;在发布一条listing的时候&#xff0c;尽可能地做到最好!在准备一条listing之前&#xff0c;一定事先要收集、整理足够多的产品关键词&#xff0c;在优化listing内容的时候填充进去。仔细观察优秀竞品…

Realrek 2.5G交换机 8+1万兆光RTL8373-VB-CG方案简介

新一代2.5G交换机方案RTL8373-VB-CG可以提供4中不同形态 a. 52.5G 电口110G光》RTL8373 b. 52.5G 电口110G电》RTL83738261 c. 82.5G 电口110G光》RTL83738224 d.82.5G 电口110G电口》RTL837382248261 1.概述 Realtek RTL8373-CG是一款低功耗、高性能、高度集成的八端口2.5G和一…

C++设计模式_19_Memento 备忘录(理解,目前多使用序列化方案来实现)

Memento 备忘录模式也属于“状态变化”模式&#xff0c;它是一个小模式&#xff0c;在今天来看有些过时&#xff0c;当今已经很少使用当前模式实现需求&#xff0c;思想却不变&#xff08;信息隐藏&#xff09;&#xff0c;目前多使用序列化方案来实现。本系列所介绍的模式&…

小程序开发——小程序项目的配置与生命周期

1.app.json配置属性 app.json配置属性 2.页面配置 app的页面配置指的是pages属性&#xff0c; pages数组的第一个页面将默认作为小程序的启动页。利用开发工具新建页面时&#xff0c;则pages属性对应的数组将自动添加该页面的路径&#xff0c;若是在硬盘中添加文件的形式则不…