开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
点赞再看,养成习惯
一、队列概述
某个周末,韩梅梅准备找李雷去逛商场。当两人路过地铁口的核酸检测点的时候,为了响应国家常态化核酸检测的号召,两个人决定排队做个核酸。
韩梅梅: 今天排队的人真多,为什么需要排队呢?不排队的话会不会早就完事了?
李雷: 排队是为了让核酸检测进展的更加高效有序,节省我们的时间。
韩梅梅: 为什么排队可以节省时间呢?
李雷: 你想啊,如果不排队是不是每个人来了都会去询问一次是否可以做核酸,那么每个人试探一遍,工作人员就不需要做别的了,只需要答复问询就够忙的了。
韩梅梅: 那排队就能解决这个问题吗?
李雷:当然可以,我们排队是遵循先到先做的原则,而且我们排队也方便工作人员统计人数,这样可以更方便的调整他们的工作策略。
韩梅梅:李雷你懂的真多。
1.1 什么是队列
队列就像我们生活中排队的映射,属于线性数据结构的一种。既然是先行数据结构,队列就同样的拥有线性结构的两种属性:
- 有序性
- 相似性(指队列中的元素具有某种相同或相近特征)
虽然队列属于线性数据结构,但是它也有它自己独特的特点。首先它对于元素的操作是在两端同时进行的,就像排队一样,在队列中数据的处理总是从第一个进入队列的元素开始从前往后进行,而数据的添加则总是在队列的末尾处,这种现象我们也可以简单的概述为'先进先出'。
1.2 图画队列
如果有小伙伴不理解先入先出的概念可以看下下图:
灰色的
元素1第一个进入队列同时也是第一个离开队列的,这就是我们说的先入先出。与栈类似,队列中操作元素的位置也是具有限制的。在栈中元素的添加和删除都是在同一端,不允许直接访问栈中间的元素。与之同理,队列也不允许直接访问位于队列中部的元素,元素的添加和出列分别在队列的两端,必须通过出列操作才可以将目标元素从队列头部取出才允许访问。
二、代码模拟
接下来我们要做的就是通过代码模拟实现一个队列结构,对于队列来说最关键的就是两个操作:入列 和出列,那么我们要做的就是将这两个方法一一进行实现。
2.1 入列
队列入列时我们要做的就是让其在队列尾部入列,并且一一有序排列:
/**
* @Classname MyQueue 模拟简单队列
* @Date 2022/10/25 14:05
* @Created by 晓龙Oba
*/
public class MyQueue<T> {
// 创建一个容纳队列的数据结构
private LinkedList<T> linkedList;
public MyQueue() {
this.linkedList = new LinkedList<>();
}
// 入列
public void add(T element) {
linkedList.add(element);
}
}
2.2 出列
出列也很简单,我们只需要移除队列头部的元素即可。
// 出列
public T poll() {
T t = linkedList.removeFirst();
return t;
}
2.3 完整代码
/**
* @Classname MyQueue 模拟简单队列
* @Date 2022/10/25 14:05
* @Created by 晓龙Oba
*/
public class MyQueue<T> {
// 创建一个容纳队列的数据结构
private LinkedList<T> linkedList;
public MyQueue() {
this.linkedList = new LinkedList<>();
}
// 入列
public void add(T element) {
linkedList.add(element);
}
// 出列
public T poll() {
T t = linkedList.removeFirst();
return t;
}
}
三、拓展
3.1 队列的应用
队列虽然结构比较简单,但是它的应用场景应该是所有数据结构中排列靠前的。比如Java源码中的锁就包含了一个TLQ队列,线程池的底层也是一个任务调度队列,我们经常使用的消息中间件也是通过任务队列实现的消息队列,因此虽然队列结构简单,但是功能并不简单。
四、手撕力扣
4.1 用两个栈实现队列
题目描述: 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
解题思路: 这是一道很简单的通过辅助栈实现队列的算法题。
首先我们需要先分别指定两个栈叫做StackA 和StackB
- 我们只需要指定StackA负责插入整数,StackB负责删除头部整数,我们直到栈有着先进后出的特性,因此每次StackA插入元素时只需要压入栈即可,当StackB需要删除头部整数时我们将StackA依次弹出栈压入StackB,这样StackB再弹栈即可:
代码实现:
public class CQueue {
private Stack<Integer> stackA;
private Stack<Integer> stackB;
public CQueue() {
stackA = new Stack<>();
stackB = new Stack<>();
}
public void appendTail(int value) {
while (!stackB.isEmpty()) {
stackA.push(stackB.pop());
}
stackA.push(value);
}
public int deleteHead() {
while (!stackA.isEmpty()) {
stackB.push(stackA.pop());
}
return stackB.isEmpty() ? -1 : stackB.pop();
}
}
运行结果:
4.2 队列的最大值
题目描述: 请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1
解题思路: 典型的辅助队列题型,考察的主要是两点:
- 队列先进先出的特性
- 当队尾插入元素大于当前元素时,此时队列的最大元素应该为队尾元素,但是当最大元素出列时,应该能够准确记录次大元素。
我们解决思路也就很简单,创建一个辅助队列(双端队列),每次插入元素时比较队尾元素大小,若插入元素大于辅助队列队尾元素则删除此元素即可。 我们可以参考下图:
代码实现:
**
* @Classname MaxQueue
* @Description TODO
* @Date 2022/10/25 15:56
* @Created by 晓龙Oba
*/
public class MaxQueue {
private Deque<Integer> queueA;
private Deque<Integer> queueB;
public MaxQueue() {
this.queueA = new ArrayDeque<>();
this.queueB = new ArrayDeque<>();
}
public int max_value() {
return queueB.peek() == null ? -1 : queueB.peek();
}
public void push_back(int value) {
queueA.offer(value);
while (!queueB.isEmpty()) {
Integer median = queueB.pollLast();
if (value <= median) {
queueB.offer(median);
break;
}
}
queueB.offer(value);
}
public int pop_front() {
Integer poll = queueA.poll();
if (poll != null && poll.equals(queueB.peek())) {
queueB.poll();
}
return poll == null ? -1 : poll;
}
public String[] execute(String[] instruction, Integer[] variable) {
String[] result = new String[instruction.length];
for (int i = 0; i < instruction.length; i++) {
if (i == 14) {
System.out.println("");
}
switch (instruction[i]) {
case "MaxQueue":
result[i] = "null";
break;
case "max_value":
result[i] = this.max_value() + "";
break;
case "pop_front":
int a = this.pop_front();
result[i] = "a";
break;
case "push_back":
this.push_back(variable[i]);
result[i] = "null";
break;
}
}
return result;
}
public static void main(String[] args) {
MaxQueue maxQueue = new MaxQueue();
String[] instruction =
{"MaxQueue", "max_value", "pop_front", "max_value", "push_back", "max_value", "pop_front", "max_value", "pop_front", "push_back", "pop_front", "pop_front", "pop_front", "push_back", "pop_front", "max_value", "pop_front", "max_value", "push_back", "push_back", "max_value", "push_back", "max_value", "max_value", "max_value", "push_back", "pop_front", "max_value", "push_back", "max_value", "max_value", "max_value", "pop_front", "push_back", "push_back", "push_back", "push_back", "pop_front", "pop_front", "max_value", "pop_front", "pop_front", "max_value", "push_back", "push_back", "pop_front", "push_back", "push_back", "push_back", "push_back", "pop_front", "max_value", "push_back", "max_value", "max_value", "pop_front", "max_value", "max_value", "max_value", "push_back", "pop_front", "push_back", "pop_front", "max_value", "max_value", "max_value", "push_back", "pop_front", "push_back", "push_back", "push_back", "pop_front", "max_value", "pop_front", "max_value", "max_value", "max_value", "pop_front", "push_back", "pop_front", "push_back", "push_back", "pop_front", "push_back", "pop_front", "push_back", "pop_front", "pop_front", "push_back", "pop_front", "pop_front", "pop_front", "push_back", "push_back", "max_value", "push_back", "pop_front", "push_back", "push_back", "pop_front"};
Integer[] variable =
{0, 0, 0, 0, 46, 0, 0, 0, 0, 868, 0, 0, 0, 525, 0, 0, 0, 0, 123, 646, 0, 229, 0, 0, 0, 871, 0, 0, 285, 0, 0, 0, 0, 45, 140, 837, 545, 0, 0, 0, 0, 0, 0, 561, 237, 0, 633, 98, 806, 717, 0, 0, 186, 0, 0, 0, 0, 0, 0, 268, 0, 29, 0, 0, 0, 0, 866, 0, 239, 3, 850, 0, 0, 0, 0, 0, 0, 0, 310, 0, 674, 770, 0, 525, 0, 425, 0, 0, 720, 0, 0, 0, 373, 411, 0, 831, 0, 765, 701, 0};
String[] execute = maxQueue.execute(instruction, variable);
System.out.println(execute);
}
}
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。 Leetcode刷题攻略已上传到gitee仓库,需要的小伙伴们可以自取: gitee.com/xiaolong-ob…
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~ 如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。