一、队列
队列是一种特殊的 线性表,特殊之处在于它只允许在表的前端(
front
)进行删除操作,而在表的后端(rear
)进行插入操作,进行插入操作的端称为队尾,进行删除操作的端称为队头。
1.1 概述
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出(
FIFO
(first in first out))的原则。即:先存入队列的数据,要先取出。后存入的要后取出。 - 示意图:
- 应用场景:银行/医院排队叫号等。
二、数组顺序队列
2.1 实现思路
-
maxSize 是该队列的最大容量。
-
因为队列的输出、输入是分别从前后端来处理,因此需要两个变量
front
及rear
分别记录队列前后端的下标,front
会随着数据输出而改变,而rear
则是随着数据输入而改变。 -
注意有两个关键判断:
- 取出元素后 , 当
front
==rear
,表示此时队列为空; - 添加元素后,若尾指针
rear
小于队列的最大下标 maxSize-1,则将数据存入rear
所指的数组元素中,否则无法存入数据。(即:rear
== maxSize - 1表示队列已满。)
- 取出元素后 , 当
-
示意图:
2.2 代码示例
- 定义数组队列规范:
public interface IArrayQueue {
/**
* 队列是否为空。
*
* @return boolean
*/
boolean isEmpty();
/**
* 队列是否已满。
*
* @return boolean
*/
boolean isFull();
/**
* 添加元素到队列。
*
* @param val 值。
*/
void add(int val);
/**
* 获取队列值。
*
* @return int
*/
int get();
/**
* 显示队列中所有值。
*/
void showQueue();
/**
* 显示队列头的值。
*/
void showHead();
}
- [数组顺序队列] 实现:
public class SequentialQueue implements IArrayQueue {
private final int maxSize;
private int front;
private int rear;
private final int[] array;
public SequentialQueue(int size) {
this.maxSize = size;
// 指向队列头的前一个位置。
front = -1;
// 指向队列尾,指向队列尾的数据(即队列最后一个数据)。
rear = -1;
array = new int[this.maxSize];
}
@Override
public boolean isEmpty() {
return rear == front;
}
@Override
public boolean isFull() {
return rear == maxSize - 1;
}
@Override
public void add(int val) {
if (isFull()) {
System.out.println("队列已满,无法继续添加元素。");
return;
}
rear++;
array[rear] = val;
}
@Override
public int get() {
if (isEmpty()) {
throw new RuntimeException("当前队列为空,无法获取元素。");
}
front++;
return array[front];
}
@Override
public void showQueue() {
if (isEmpty()) {
System.out.println("当前队列为空。");
return;
}
for (int i = 0; i < array.length; i++) {
System.out.printf("array[%d]=%d\n", i, array[i]);
}
}
@Override
public void showHead() {
if (isEmpty()) {
System.out.println("当前队列为空。");
return;
}
System.out.printf("head=%d\n", array[front + 1]);
}
}
- 定义控制台输入方法(用于测试):
public class ArrayQueueContext {
/**
* 接收用户输入指令。
*
* @param queue 队列
*/
public void userInput(IArrayQueue queue) {
Scanner sc = new Scanner(System.in);
char cmd;
boolean isLoop = true;
// 循环接收输入。
while (isLoop) {
// 指令类型。
System.out.println("s(show): 显示队列");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
System.out.println("e(exit): 退出程序");
cmd = sc.next().charAt(0);
switch (cmd) {
case 's':
queue.showQueue();
break;
case 'a':
System.out.println("请输入一个数字:");
int val = sc.nextInt();
queue.add(val);
break;
case 'g':
try {
int value = queue.get();
System.out.println("取出的值为:" + value);
} catch (Exception e) {
System.out.println("取值异常:" + e.getMessage());
}
break;
case 'h':
queue.showHead();
break;
case 'e':
sc.close();
isLoop = false;
break;
default:
break;
}
}
System.out.println("程序退出~");
}
}
- 测试:
备注:若想使用
@Test
注解执行控制台用户输入,需将bin\idea64.exe.vmoptions
添加开关参数-Deditable.java.test.console=true
。
public class ArrayQueueTests {
/**
* 测试数组顺序队列。
*/
@Test
public void testArrayQueue() {
ArrayQueueContext context = new ArrayQueueContext();
context.userInput(new SequentialQueue(3));
}
}
2.3 问题分析及优化
- 测试结果:
- 如图所示,目前顺序数组队列只能使用一次, 没有达到复用的效果。
- 将这个数组使用算法,改进成一个环形的队列 取模:
%
。
三、数组环形队列
3.1 实现思路
-
对前面的数组顺序队列的优化,若想充分利用数组, 因此需要将数组看做是一个环形的结构。(通过取模的方式来实现即可),具体调整如下。
-
front
指针直接指向队列第一个元素,即front
初始值为 0 ,而rear
指针指向队列最后一个元素的后一个位置,rear
的初始值也为 0。 -
队满判断条件优化为: (rear + 1) % maxSize == front,而队空判断条件依旧为 rear == front。
-
队列中有效的数据个数为: (rear + maxSize - front)% maxSize。
-
由综上条件的变化,我们可以得到一个数组环形队列。
3.2 代码示例
- [数组环形队列] 实现:
public class CircleArrayQueue implements IArrayQueue {
private final int maxSize;
private int front = 0;
private int rear = 0;
private final int[] array;
public CircleArrayQueue(int size) {
this.maxSize = size;
this.array = new int[this.maxSize];
}
@Override
public boolean isEmpty() {
return rear == front;
}
@Override
public boolean isFull() {
// 假设 maxSize=4,此时队首为1,则当队尾为4是表示队列已满。
return (rear + 1) % maxSize == front;
}
@Override
public void add(int val) {
if (isFull()) {
System.out.println("队列已满,无法继续添加元素。");
return;
}
// 队尾插值。
array[rear] = val;
rear = (rear + 1) % maxSize;
}
@Override
public int get() {
if (isEmpty()) {
throw new RuntimeException("队列为空,无法取出数据");
}
// 队首取值。
int value = array[front];
front = (front + 1) % maxSize;
return value;
}
private int size() {
// 假设 maxSize=4,rear=2,front=1,则此时的队列数据个数为1个。
return (rear + maxSize - front) % maxSize;
}
@Override
public void showQueue() {
if (isEmpty()) {
System.out.println("队列为空");
return;
}
// 从 front 开始遍历,遍历多少个元素。
for (int i = front; i < front + size(); i++) {
System.out.printf("array[%d]=%d\n", (i % maxSize), array[(i % maxSize)]);
}
}
@Override
public void showHead() {
if (isEmpty()) {
System.out.println("队列为空");
return;
}
System.out.printf("head=%d\n", array[front]);
}
}
- 测试:
public class ArrayQueueTests {
/**
* 测试数组环形队列。
*/
@Test
public void testCircleArrayQueue() {
ArrayQueueContext context = new ArrayQueueContext();
// 创建环形队列,设置大小为4, 其队列的有效数据最大是3。
context.userInput(new CircleArrayQueue(4));
}
}
3.3 优化后结果
四、结束语
“-------怕什么真理无穷,进一寸有一寸的欢喜。”
微信公众号搜索:饺子泡牛奶。