1. 用 Java 模拟队列
1.1 什么是队列?
队列(Queue)是一种基于先进先出原则的线性数据结构,数据项在队列末尾添加,从队列头部删除。
1.2 模拟循环队列
用java简单模拟单链表和双向链表 and LinkedList类的介绍 - 掘金 (juejin.cn)
我们知道 队列 是队尾进、队头出,那么我们用什么来储存队列的数据呢?Java中的 LinkedList(双向链表)实现了Queue接口,也就是说Java中的队列的底层是用双向链表实现的,入队就是尾插、出队就是头删,效率是很高的。
还有一种方式,就是用数组来模拟队列,用两个指针(变量)来维护队头与队尾,入队尾指针后移一位,出队头指针后移一位。但是这会遇到一个问题,由于数组是静态的,尾指针总有移到最后一个位置的时候,这时如果扩容,前面的空间就会被浪费掉(头指针会后移)。这时就用循环队列来储存数据,就如下图。
(1)在开始模拟实现前,先约定当front == trail时队列为空,当(trail + 1)% length == front时队列满
public class MyQueue {
private int[] data;
public int front;
public int trail;
//构造 k 大小的队列
public MyQueue(int k){
this.data = new int[k+1];//为什么是 k+1 ?因为要装k个数据,多的一个用来判断队列满。
}
//判断空
public boolean isEmpty(){
return this.front == this.trail;
}
//判断满
public boolean isFull(){
return (trail + 1) % data.length == front;
}
}
(2)入队:这里没有进行扩容,大家可以自己写一个扩容的版本。
//入队
public boolean offer(int val){
if(isFull()){
return false;
} else {
data[trail] = val;
trail = (trail + 1) % data.length;//为什么这么写?想一想 trail 为数组的最后一个时。
}
return true;
}
(3)出队:
//出队
public boolean poll(){
if(isEmpty()){
return false;
}else {
front = (front + 1) % data.length;
return true;
}
}
(4)查看队头元素:
//查看队头
public int peek() {
if (isEmpty()) {
return -1;
} else {
return this.data[front];
}
}
(4)计算队列中数据的个数:
//计算队列中数据的个数
public int size(){
return (trail - front + data.length) % data.length;
}
假设capacity为数组的长度。
- 首先,计算队列中元素的数量,即
tail - head。 - 如果
tail - head为正,说明尾部指针在头部指针之后,即没有绕回数组的开头位置,此时直接返回tail - head即可。 - 如果
tail - head为负,说明尾部指针在头部指针之前,即绕回了数组的开头位置。这种情况下,我们需要将tail - head加上数组的长度capacity,才能得到队列中元素的数量。 - 最后,使用
% capacity将结果取模,为什么要取模?因为出现第 2 种情况的时候多加了一个capacity。
(本篇只实现了常用的方法)
1.3 汇总
public class MyQueue {
private int[] data;
public int front;
public int trail;
//构造 k 大小的队列
public MyQueue(int k){
this.data = new int[k+1];//为什么是 k+1 ?因为要装k个数据,多的一个用来判断队列满。
}
//判断空
public boolean isEmpty(){
return this.front == this.trail;
}
//判断满
public boolean isFull(){
return (trail + 1) % data.length == front;
}
//入队
public boolean offer(int val){
if(isFull()){
return false;
} else {
data[trail] = val;
trail = (trail + 1) % data.length;
}
return true;
}
//出队
public boolean poll(){
if(isEmpty()){
return false;
}else {
front = (front + 1) % data.length;
return true;
}
}
//查看队头
public int peek() {
if (isEmpty()) {
return -1;
} else {
return this.data[front];
}
}
//计算队列中数据的个数
public int size(){
return (trail - front + data.length) % data.length;
}
}
2. Queue接口
2.1 Queue 接口的介绍
Queue是一个接口,有多个实现类,包括LinkedList和PriorityQueue(后面更新)。LinkedList实现了Dueue接口,而Dueue继承了Queue接口,所以它可以用作普通的队列,也可以用作双向队列Deque。
Deque 是双向队列,元素可以从队头出队和入队,也可以从队尾出队和入队,本篇就不做介绍了,大家可以自己试一试它的方法。
2.2 Queue接口提供的常用方法
| 常用方法 | 描述 |
|---|---|
boolean add(E e) | 添加元素到队列末尾,如果队列已满,则抛出异常 |
boolean offer(E e) | 添加元素到队列末尾,如果队列已满,则返回false |
E remove() | 移除并返回队列头部的元素,如果队列为空,则抛出异常 |
E poll() | 移除并返回队列头部的元素,如果队列为空,则返回null |
E element() | 返回队列头部的元素,如果队列为空,则抛出异常 |
E peek() | 返回队列头部的元素,如果队列为空,则返回null |
boolean isEmpty() | 检查队列是否为空 |
int size() | 返回队列中的元素数量 |
void clear() | 清空队列中的所有元素 |
需要注意的是,Queue接口继承了Collection接口,因此它也包含了许多与Collection接口相同的方法。
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// 添加元素到队列末尾
queue.add("apple");
queue.add("banana");
queue.add("cherry");
// 返回队列头部的元素,但不移除它
String first = queue.peek();
System.out.println("队列头部的元素是:" + first);
// 移除并返回队列头部的元素
String item = queue.poll();
System.out.println("移除的元素是:" + item);
// 返回队列中的元素数量
int size = queue.size();
System.out.println("队列中的元素数量是:" + size);
// 清空队列中的所有元素
queue.clear();
}
结果:
队列头部的元素是:apple
移除的元素是:apple
队列中的元素数量是:2