在对Java实现数组模拟队列零了解的情况下,建议先去阅读《JAVA实现数组模拟单向队列的思路及代码》一文,可以辅助理解本文核心思想。
实现:让数组达到复用的效果,即:当我们从数组队列中取出了数据,那取出数据后后这个空间可以再次使用。
单向数组队列实现中,我们设立了如下几个类属性及其含义:
那么我们将如何确立环形数组队列类属性的含义才能达到我们的目的呢?
看完以上属性的定义,我们来先思考以下几个公式实现环形数组队列的原因:
看完以上公式,我想现在的你恐怕已经开始陷入迷茫当中了,为什么这样定义类属性就可以实现环形数组队列?
请别着急,我们将逐一进行图解分析!
根据以上属性定义,较单向数组队列来说,仅仅只是队列前后指针标识初始位置发生变化(front = rear = 0)
/**
* ClassName: AnnularArrQueue
* Package: com.zhao.test
* Description:
*
* @Author XH-zhao
* @Create 2023/3/25 20:18
* @Version 1.0
*/
public class AnnularArrQueue {
private final int[] arrQueue; // 创建的数组队列的地址引用
private final int arrMaxSize; // 代表创建的该环形队列数组的容量,及队列最大容量+一个作为rear指针标识的约定
private int front; // 用于指向队列头部的位置
private int rear; // 用于指向队列尾部成员的后一个空间(约定)
/**
* 环形数组队列构造函数
*
* @param arrMaxSize 传入想要构造的队列长度
*/
public AnnularArrQueue(int arrMaxSize) {
this.arrMaxSize = arrMaxSize;
arrQueue = new int[this.arrMaxSize];
// 将前后标记均指向队列的最前方(0位置)
front = 0;
rear = 0;
}
}
class Test {
public static void main(String[] args) {
// 创建一个环形数组队列
AnnularArrQueue annularArrQueue = new AnnularArrQueue(4);
}
}
通过以上环形数组队列类的设计,就可创建一个如下的环形数组队列对象。(当然,目前还没有实现环形的作用)
当队列为空的条件公式:rear == front
公式图解:
当队列前后指针相等时环形数组队列为空,这一点和单向数组队列没有什么区别。
这一点也很好理解,即从rear=front=0开始,增加第一个队列成员时,rear要向前移一位,此时front指向该队列成员。
当我们拿取Member1后,front向后移动就会再次和rear相等,此时队列为空。
代码实现:
/**
* 判断环形队列是否为空
*
* @return
*/
public boolean isEmpty() {
return front == rear; // 队列头部指针==队列尾部指针,说明队列为空
}
在判断队列是否为满之前,我们先来理解一下上文所说的预留一个数组空间做约定的含义。
我们可以通过如下我从网上获取的分析图片来理解一下为什么要存在一个数组空间作为约定。
从图中我们可以看出,如果我们不去预留一个空间做约定的话,rear指针继续向前装队列成员,以至于rear指针与front指针相等,但是此刻环形数组却是满的状态,无法通过rear==front来判断环形队列到底是空还是满的状态,所以要牺牲一个数组空间作为约定,让rear在向后移的过程中判断是否将要和front相遇,来确定是否停止添加队列成员。所以也解释了为什么(maxSize - 1)作为该环形队列数组的最大存储容量。
当队列已满的条件公式:(rear + 1) % maxSize == front
公式图解:
通过上述说明我们知道,环形队列的实现需要牺牲一个数组空间作为约定。我们知道如果环形队列满了,那么rear指针和front之间的距离就只差一个约定空间就可以实现rear==front,那这样的话我们可以设想如下两种环形数组队列满了的情况。
情况一:rear指针在front指针之后
即为rear绕到了front的后面,在这种情况下,rear + 1 == front 等价于【(rear + 1) % maxSize == front】即能证明环形队列已满。
情况二:rear指针在front指针之前
在这种情况下,我们发现rear + 1 == maxSize != front 所以不能以 rear + 1 == front 作为判断环形数组队列已满的条件。
【(rear + 1) % maxSize == front】仍能适用于此情况。
所以通过判断【(rear + 1) % maxSize == front】即可判断环形数组是否已满。
代码实现:
/**
* 判断环形队列是否已满
*
* @return
*/
public boolean isFull() {
return (rear + 1) % arrMaxSize == front;
}
代码实现:
/**
* 添加队列成员到队列
*
* @param member
*/
public void addQueueMember(int member) {
// 判断队列是否已满
if (isFull()) {
System.out.println("队列已满,不能往队列中添加数据。");
return;
}
arrQueue[rear] = member; // 直接将数据加入
rear = (rear + 1) % arrMaxSize; // 将rear后移,考虑取模,即当rear到数组末尾时取模归零
}
/**
* 从队列中拿取队列成员
*
* @return
*/
public int getQueueMember() {
// 判断队列是否为空
if (isEmpty()) {
throw new RuntimeException("队列为空,不能从队列获取数据。");
}
int value = arrQueue[front]; // 先把front对应的值保留到一个临时变量
front = (front + 1) % arrMaxSize; // 将front后移,考虑取模,即当front到数组末尾时取模归零
return value; // 将临时保存的变量返回
}
即环形数组中从front开始到rear结束的成员个数
队列中有效的成员的个数条件公式:(rear + maxSize - front) % maxSize
公式图解:
情况一:rear在front之前
情况二:rear在front之后
代码实现:
/**
* 求出当前队列有效成员的个数
*
* @return
*/
public int validMemberNum() {
return (rear + arrMaxSize - front) % arrMaxSize;
}
代码实现:
/**
* 显示队列中所有成员
*/
public void showQueueMemberAll() {
if (isEmpty()) {
System.out.println("队列是空的,没有数据。");
}
// 从front开始遍历,遍历多少个元素
for (int i = front; i < front + validMemberNum(); i++) {
System.out.printf("arr[%d]=%d\n", i % arrMaxSize, arrQueue[i % arrMaxSize]);
}
}
/**
* 显示队列的头部成员
*
* @return
*/
public int headQueueMember() {
if (isEmpty()) {
throw new RuntimeException("队列是空的,没有数据。");
}
return arrQueue[front];
}
import java.util.Scanner;
/**
* ClassName: AnnularArrQueue
* Package: com.zhao.test
* Description:
*
* @Author XH-zhao
* @Create 2023/3/25 20:18
* @Version 1.0
*/
public class AnnularArrQueue {
private final int[] arrQueue; // 创建的数组队列的地址引用
private final int arrMaxSize; // 代表创建的该环形队列数组的容量,及队列最大容量+一个作为rear指针标识的约定
private int front; // 用于指向队列头部的位置
private int rear; // 用于指向队列尾部成员的后一个空间(约定)
/**
* 环形数组队列构造函数
*
* @param arrMaxSize 传入想要构造的队列长度
*/
public AnnularArrQueue(int arrMaxSize) {
this.arrMaxSize = arrMaxSize;
arrQueue = new int[this.arrMaxSize];
// 将前后标记均指向队列的最前方(0位置),默认即为0,所以这里也可以省略显式赋值
front = 0;
rear = 0;
}
/**
* 判断环形队列是否为空
*
* @return
*/
public boolean isEmpty() {
return front == rear; // 队列头部指针==队列尾部指针,说明队列为空
}
/**
* 判断环形队列是否已满
*
* @return
*/
public boolean isFull() {
return (rear + 1) % arrMaxSize == front;
}
/**
* 添加队列成员到队列
*
* @param member
*/
public void addQueueMember(int member) {
// 判断队列是否已满
if (isFull()) {
System.out.println("队列已满,不能往队列中添加数据。");
return;
}
arrQueue[rear] = member; // 直接将数据加入
rear = (rear + 1) % arrMaxSize; // 将rear后移,考虑取模,即当rear到数组末尾时取模归零
}
/**
* 从队列中拿取队列成员
*
* @return
*/
public int getQueueMember() {
// 判断队列是否为空
if (isEmpty()) {
throw new RuntimeException("队列为空,不能从队列获取数据。");
}
int value = arrQueue[front]; // 先把front对应的值保留到一个临时变量
front = (front + 1) % arrMaxSize; // 将front后移,考虑取模,即当front到数组末尾时取模归零
return value; // 将临时保存的变量返回
}
/**
* 求出当前队列有效成员的个数
*
* @return
*/
public int validMemberNum() {
return (rear + arrMaxSize - front) % arrMaxSize;
}
/**
* 显示队列中所有成员
*/
public void showQueueMemberAll() {
if (isEmpty()) {
System.out.println("队列是空的,没有数据。");
}
// 从front开始遍历,遍历多少个元素
for (int i = front; i < front + validMemberNum(); i++) {
System.out.printf("arr[%d]=%d\n", i % arrMaxSize, arrQueue[i % arrMaxSize]);
}
}
/**
* 显示队列的头部成员
*
* @return
*/
public int headQueueMember() {
if (isEmpty()) {
throw new RuntimeException("队列是空的,没有数据。");
}
return arrQueue[front];
}
}
class Test {
public static void main(String[] args) {
//测试--创建一个队列
AnnularArrQueue queue = new AnnularArrQueue(4);
char key = ' ';// 接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while (loop) {
System.out.println("s(show):显示队列");
System.out.println("e(exit):退出程序");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):从队列取出数据");
System.out.println("h(head):查看队列头的数据");
key = scanner.next().charAt(0); // 接收用户输入的第一个字符
switch (key) {
case 's' -> queue.showQueueMemberAll();
case 'e' -> {
scanner.close();
loop = false;
System.out.println("程序退出");
}
case 'a' -> {
System.out.println("请输入一个整数");
int a = scanner.nextInt();
queue.addQueueMember(a);
}
case 'g' -> {
try {
int res = queue.getQueueMember();
System.out.println(res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
case 'h' -> {
try {
int head = queue.headQueueMember();
System.out.println(head);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
}
}
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
队列是空的,没有数据。
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
2
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
arr[0]=2
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
3
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
arr[0]=2
arr[1]=3
arr[2]=5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
9
队列已满,不能往队列中添加数据。
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
arr[0]=2
arr[1]=3
arr[2]=5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
g
2
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
arr[1]=3
arr[2]=5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
g
3
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
g
5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
g
队列为空,不能从队列获取数据。
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
s
队列是空的,没有数据。
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
3
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
a
请输入一个整数
5
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
h
3
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
h
3
s(show):显示队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列取出数据
h(head):查看队列头的数据
e
程序退出
从上述整体实验中可以看出,环形数组队列类的设计,完全呈现出了队列存储数据先进先出的特点。同时实现数组空间复用的效果!