0.前言
在数据结构与算法中,队列是被经常使用的一种数据结构,总体上构成较为简单,但是Java在实际使用时易用错,经常会。比如 poll() 方法,add() 方法,offer() 方法,addFirst()方法,removeFirst()方法,removeLast() 方法,它们之间的对应关系是怎么样的,Queue和Deque使用和功能有何不同?本文主要探究这些问题。
1.队列这种数据结构
作为一种经典的数据结构,栈承担了先进后出的责任,而在先进先出这方面,则由队列来完成。队列有单向队列,即最经典的队列,一头进,另一头出,而双端队列,两端都可以进出,比较灵活,所以造成了双端队列的api较多,在实际使用中经常会搞错方向。
还有就是队列的头尾问题,分不清很容易在双端队列的removeFirst()方法或者是removeLast()方法中混淆。队列中的元素从哪边出去就是队列头,双端队列中两边都可以出去,但是沿着元素进入的方向能走出队列的一端为头。
需要注意的是,队列是操作受限的线性表,所以,不是任何对线性表的操作都可以作为队列的操作。比如,不可以随便读取队列中间的某个数据。【3】
关于队列的结构还可参考这篇文章,有助于理解:
栈和队列相互实现 (用队列实现栈/用栈实现队列) 超详细~
2.Java Queue
在Java中,Queue是队列的接口,主要的方法有6个,比较简单。常用的实现类有两个:LinkedList<>()和ArrayDeque<>()。
官方文档:
1.add() 方法
添加元素到队尾
2.element()方法
查看队首元素
3.offer()方法
添加元素到队尾,和add()作用相同。队列有大小限制,在一个满的队列中加入一个新项,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false,因此就可以在程序中进行有效的判断。【1】
4.peek()方法
查看队首元素,和peek()相同。队列为空, element()方法会抛出一个异常,peek() 返回 null。
5.poll()方法
删除队首元素
6.remove()方法
删除队首元素,和poll()方法相同。如果队列元素为空,调用remove()会出现异常,而 poll() 方法返回null。
7.其他方法
其他还有isEmpty()方法查看是否为空,size()方法查看队列长度等。
8.案例解析
可参考【3】
3.Java Deque
双端队列的接口是Deque,和Queue一样,常见的实现的类都是LinkedList<>()和ArrayDeque<>(),因为两边都可以插入、删除元素,所以方法比较多。
1.插入元素
add() 添加元素到队尾,空间不足报异常,成功返回true。
addFirst() 添加元素到队头,空间不足报异常。
offerFirst() 添加元素到队头,成功返回true,否则返回false。
addLast() 添加元素到队尾,空间不足报异常。
offerLast() 添加元素到队尾,成功返回true,否则返回false。
push() 添加元素到队头,此处承担栈的作用,等价于addFirst(),空间不足报异常。
2.删除元素
remove() 删除头元素
removeFirst() 删除头元素,为空报异常。
removeLast() 删除尾元素,为空报异常。
pollFirst() 删除头元素,为空返回null。
pollLast() 删除尾元素,为空返回null。
pop() 删除头元素,此处承担栈的作用,等价于removeFirst(),为空报异常。
3.查看元素
getFirst() 查看头元素,为空报异常。
getLast() 查看尾元素,为空报异常。
peekFirst() 查看头元素,为空返回null。
peekLast() 查看尾元素,为空返回null。
4.案例演示
可参考【2】
4.辨析
在整体上,Queue可以看作是Deque的子集,Queue有的功能Deque都有,因为是双端队列,两边都可以插入和删除,所以Deque要比Queue更灵活,在有些题目中,会出现必须要双端队列的情况,因为两边都需要删除,而Queue做不到这一点。
还有,在一些题目中,会要求只能用队列,不能使用双端队列,需要注意。另外,由于双端队列可以先进后出,也可以当作栈来使用,例如经典的问题,使用队列实现栈,可以用一个双端队列实现,也可以用两个队列实现。
等效方法【1】:
5.leetcodde案例
1. leetcode225 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
class MyStack {
Deque<Integer> list1;
Deque<Integer> list2;
public MyStack() {
list1 = new ArrayDeque<>();
list2 = new ArrayDeque<>();
}
public void push(int x) {
list2.add(x);
while(!list1.isEmpty()){
list2.add(list1.remove());//add poll
}
Deque<Integer> temp = list1;
list1 = list2;
list2 = temp;
}
public int pop() {
return list1.remove();
}
public int top() {
return list1.peek();
}
public boolean empty() {
return list1.isEmpty();
}
}
本题小结
(1)一个主队列,一个辅助队列,主队列时刻保持最新状态
(2)主队列保持最新就意味着(top/Empty/pop)等操作都对最新的队列即主队列操作
(3)来一个新的元素放到辅助队列,然后把主队列的元素都丢到辅助队列,交换即最新
以题中的案例为例,画出主辅队列的变化:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
此题目也可以使用一个双端队列来实现
可见另一篇文章:
栈和队列相互实现 (用队列实现栈/用栈实现队列) 超详细~
2. leetcode232 用栈实现队列
可见这篇文章,比较详细 栈和队列相互实现 (用队列实现栈/用栈实现队列) 超详细~
3. leetcode102 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
Deque<TreeNode> queue = new LinkedList<>();
if(root == null) return list;
queue.addLast(root);
while(!queue.isEmpty()){
int len = queue.size();
List<Integer> temp = new ArrayList<>();
for(int i = 0; i < len; i++){
TreeNode cur = queue.pollFirst();
temp.add(cur.val);
if(cur.left != null){
queue.addLast(cur.left);
}
if(cur.right != null){
queue.addLast(cur.right);
}
}
list.add(temp);
}
return list;
}
}
详细解析:
Java-数据结构-二叉树<三>
4.leetcode 面试题59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
class MaxQueue {
Deque<Integer> deque1;
Deque<Integer> deque2;
public MaxQueue() {
deque1 = new LinkedList<>();
deque2 = new LinkedList<>();
}
public int max_value() {
if(deque2.isEmpty()) return -1;
return deque2.peekFirst();
}
public void push_back(int value) {
deque1.addLast(value);
while(!deque2.isEmpty() && value > deque2.peekLast()){
deque2.removeLast();
}
deque2.addLast(value);
}
public int pop_front() {
if(deque1.isEmpty()){
return -1;
}
if(deque1.peekFirst().equals(deque2.peekFirst())){
deque2.removeFirst();
return deque1.removeFirst();
}
return deque1.removeFirst();
}
}
本题小结
(1)一个主队列,一个辅助队列,主队列存所有的元素,而辅助队列只存最大值
(2)一个新的队列元素来到之后,放到主队列,而在辅助队列中,要依次比较新值和辅助队列中的值
(3)在去除元素时,要注意辅助队列和主队列元素是否一致,主队列的元素一直比辅助队列多,因为辅助队列只保存最大值
6.参考来源
【1】CSDN Jason&Zhou Java数据结构之Deque(双端队列)
【2】CSDN onedegree java关于Deque的使用
【3】CSDN Sueko Java中队列(Queue)用法
【4】Leetcode Krahets(K神) 剑指 Offer 59 - II. 队列的最大值(单调双向队列,清晰图解)