面试官再问你优先级队列,请把这篇文章丢给他
程序员常用的IDEA插件:https://github.com/silently9527/ToolsetIdeaPlugin
完全开源的淘客项目:https://github.com/silently9527/mall-coupons-server
微信公众号:贝塔学Java
前言
假如你设计的事件系统中有很多的事件,每个事件都定义了不同的权重值,系统需要优先处理权重较高的事件,这里你就需要使用到优先级队列,本篇我们一起来学习实现优先级队列的常用方式
队列API定义
在实现之前,首先我们需要先定义出优先级队的API,优先级队列是一种抽象的数据结构,我们依然可以基于前面我们使用到的队列API来修改;需要了解之前的队列的实现可以查看《面试的季节到了,老哥确定不来复习下数据结构吗》
public interface Queue<T> extends Iterable<T> {void enqueue(T item); //入队列T dequeue(); //出队列int size();boolean isEmpty();
}
其中的入队列enqueue
和出队列dequeue
是我们主要需要实现的方式,也是优先级队列的核心方法
初级版本的实现
队列API的抽象类
public abstract class AbstractQueue<T> implements Queue<T> {private Comparator<T> comparator;public AbstractQueue(Comparator<T> comparator) {this.comparator = comparator;}public boolean less(T a, T b) {return comparator.compare(a, b) < 0;}public void exch(T[] array, int i, int j) {T tmp = array[i];array[i] = array[j];array[j] = tmp;}
}
基于无序数组实现
实现优先级队列的最简单实现可以参考《面试的季节到了,老哥确定不来复习下数据结构吗》中栈的实现方式,enqueue
和栈的push
方式实现方式一致,dequeue
可以参考选择排序的实现,循环一遍数组,找出最大值和数组最后一个元素交换,然后删除它;
public class DisorderPriorityQueue<T> extends AbstractQueue<T> {private T[] queue;private int size;public DisorderPriorityQueue(int max, Comparator<T> comparator) {super(comparator);this.queue = (T[]) new Object[max];}@Overridepublic void enqueue(T item) {queue[size++] = item;}@Overridepublic T dequeue() {int index = 0;for (int i = 1; i < size; i++) {if (less(queue[index], queue[i])) {index = i;}}size--;exch(queue, index, size);T data = queue[size];queue[size] = null;return data;}//省略其他函数
}
这里只实现了定长的优先级队列,如何实现自动扩容呢?也可以参考这篇文章《面试的季节到了,老哥确定不来复习下数据结构吗》;基于无序数组实现的enqueue时间复杂度是O(1),dequeue时间复杂度是O(n)
基于有序数组实现
基于有序数组实现就是在入队的时候保证数组有序,那么在出队列的时候可以直接删掉最大值;插入的过程和插入排序类似的操作
public class OrderPriorityQueue<T> extends AbstractQueue<T> {private T[] queue;private int size;public OrderPriorityQueue(int max, Comparator<T> comparator) {super(comparator);this.queue = (T[]) new Object[max];}@Overridepublic void enqueue(T item) {queue[size++] = item;for (int i = size - 1; i > 1 && less(queue[i], queue[i - 1]); i--) {exch(queue, i, i - 1);}}@Overridepublic T dequeue() {size--;T data = queue[size];queue[size] = null;return data;}//省略其他函数
}
enqueue时间复杂度是O(n),dequeue时间复杂度是O(1)
基于链表实现
基于链表的实现与上面的类似,有兴趣的可以自己实现
在《面试的季节到了,老哥确定不来复习下数据结构吗》中我们实现的栈和队列的操作都能够在常数时间内完成,但是优先级队列从上面的实现过程,我们发现初级版本的实现插入或删除最大值的操作最糟糕的情况会是线性时间。
二叉堆实现
二叉堆的定义
在二叉堆中,每个节点都将大于等于它的子节点,也成为堆有序;其中根节点是最大的节点。
二叉堆的表示:
重点:
在一个二叉堆中,位置k节点的父节点的位置为k/2
,它的两个子节点的位置为2k
和2k+1
; 基于这点,我们可以用数组来表示二叉堆,通过移动数组的下标来找到节点父节点和子节点
在元素进行插入和删除操作的过程中,会破坏堆有序,所以我们需要做一些操作来保证堆再次有序;主要有两种情况,当某个节点的优先级上升,我们需要由下向上恢复堆有序(下沉);当某个节点优先级下降,我们需要由上向下恢复堆有序(上浮)
由上向下恢复堆有序(上浮)
private void swim(int k) {while (k > 0 && less(queue[k / 2], queue[k])) {exch(queue, k / 2, k);k = k / 2;}
}
根据当前的节点k找到父节点的位置k/2,比较当前节点和父节点,如果比父节点大就交换,直到找个比当前节点大的父节点或者已上浮到了根节点
由下向上恢复堆有序(下沉)
private void sink(int k) {while (2 * k <= size) {int i = 2 * k;if (less(queue[i], queue[i + 1])) {i++;}if (less(queue[i], queue[k])) {break;}exch(queue, i, k);k = i;}
}
二叉堆实现优先级队列
- 入队操作:将新的元素添加到数组末尾,让新元素上浮到适合位置,增加堆的大小
- 出队操作:将最大的根节点删除,然后把最后一个元素放入到顶端,下层顶端元素到合适位置,减小堆大小
public class BinaryHeapPriorityQueue<T> extends AbstractQueue<T> {private T[] queue;private int size;public BinaryHeapPriorityQueue(int max, Comparator<T> comparator) {super(comparator);this.queue = (T[]) new Object[max + 1];}@Overridepublic void enqueue(T item) {this.queue[++size] = item;this.swim(size);}@Overridepublic T dequeue() {T max = this.queue[1];exch(this.queue, 1, size--);this.queue[size + 1] = null; //释放内存this.sink(1);return max;}//省略其他函数
}
注意:
由于我们为了方便计算父节点和子节点的索引位置,所以数组中的第一个位置是不会使用的;可以自己思考下使用第一个位置,那么子节点和父节点的位置应该如何计算?
基于堆的实现,入队和出队的时间复杂对都是logN,解决了初级版本实现的问题。
数组大小动态扩容和缩容依然可以参考之前栈的实现方式
文中所有源码已放入到了github仓库https://github.com/silently9527/JavaCore
最后(点关注,不迷路)
文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。
最后,写作不易,请不要白嫖我哟,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源
面试官再问你优先级队列,请把这篇文章丢给他相关推荐
- websphere mq 查看队列中是否有数据_如果面试官再问你消息队列,就把这篇甩给他!...
★★★建议星标我们★★★ 公众号改版后文章乱序推荐,希望你可以点击上方"Java进阶架构师",点击右上角,将我们设为★"星标"!这样才不会错过每日进阶架构文章呀 ...
- 面试官再问我如何保证 RocketMQ 不丢失消息,这回我笑了!
0x00. 消息的发送流程 一条消息从生产到被消费,将会经历三个阶段: 生产阶段,Producer 新建消息,然后通过网络将消息投递给 MQ Broker 存储阶段,消息将会存储在 Broker 端磁 ...
- 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看
来自:烟雨星空 前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希 ...
- 程序员过关斩将--面试官再问你Http请求过程,怼回去!
Http介绍 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议.所有的WWW文件都必须遵守这个标准.设计HTTP最初的目的是为了提 ...
- 查询已有链表的hashmap_面试官再问你 HashMap 底层原理,就把这篇文章甩给他看...
前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...
- qt 如何 指针 自动 释放内存_要是面试官再问你智能指针的问题,就拿这篇文章“盘他”!!!...
前一段时间,有不少朋友问我关于智能指针的问题,并且反映经常会在面试中被面试官问到,所以今天小豆君就来讲讲我对智能指针的理解,希望能对大家有所帮助 既然讲智能指针,我们就先来看看它为什么会出现. 1 传 ...
- 面试官再问高并发,求你把这篇发给他!
高并发,几乎是每个程序员都想拥有的经验.原因很简单:随着流量变大,会遇到各种各样的技术问题,比如接口响应超时.CPU load升高.GC频繁.死锁.大数据量存储等等,这些问题能推动我们在技术深度上不断 ...
- 如果有人再问你 Java IO,把这篇文章砸他头上
一.简介 说到 I/O,想必大家都不会陌生, I/O 英语全称:Input/Output,即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出. 比如我们常用的 SD 卡.U ...
- 面试官再问线程池,你这样谈谈线程的回收,好感会倍增!
来源 | https://www.cnblogs.com/kingsleylam/p/11241625.html 最近阅读了JDK线程池ThreadPoolExecutor的源码,对线程池执行任务的流 ...
最新文章
- 技术/领域专家有什么要求?
- Android 简单基站定位程序
- Team Foundation Server 2010 安装、部署与配置(二):安装之前的预备工作 .
- 云+AI+5G时代,华为云已准备好多元化云服务架构
- visual studio code typescript 防止 import statement 断行
- 使用aliyun镜像源下载镜像及仓库搭建
- json解析库go-simplejson使用
- 从零开始的FPGA学习(2)(用三八译码器实现一位全加器)
- maven导入ojdbc14.jar
- GOPS2016全球运维大会•上海站进入倒计时 最全参会攻略震撼来袭
- 流程图制作: BPMN流程图在线绘制
- 6G八大关键技术(国泰君安团队)
- 初等证明:使用无穷下降法证明丢番图方程x^4 - y^4 = z^2无非零整数解
- 网页常见的图标图片格式的区别
- 岭南东方品牌连签两店,持续发力旅游目的地
- Soft Diffusion:谷歌新框架从通用扩散过程中正确调度、学习和采样
- Python爬虫:给我一个链接,西瓜视频随便下载
- 计算机网络电视如何配置,关于电脑控制网络电视的方法
- 联通软研院2020年球季校招笔试第三题 20190916
- 屌丝程序员和技术大拿的区别是什么?