数组模拟环形队列的思路及代码

JAVA实现数组模拟环形队列的思路及代码

前言

在对Java实现数组模拟队列零了解的情况下,建议先去阅读《JAVA实现数组模拟单向队列的思路及代码》一文,可以辅助理解本文核心思想。

一、环形数组队列

实现:让数组达到复用的效果,即:当我们从数组队列中取出了数据,那取出数据后后这个空间可以再次使用。

二、创建环形数组队列类前思考

单向数组队列实现中,我们设立了如下几个类属性及其含义:

  1. front:用于指向队列头部前一个的位置
  2. rear:用于指向队列尾部,用来指向队列尾部数据的一个指针
  3. maxSize:代表创建的该单向队列数组的最大存储容量
  4. front和rear的初始值都是-1。

那么我们将如何确立环形数组队列类属性的含义才能达到我们的目的呢?

  1. front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
  2. rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定,(即尾索引的下一个为头索引时表示队列满)
  3. 所以我们将front和rear的初始值都设为0
  4. maxSize - 1:代表创建的该环形队列数组的最大存储容量,其中留下一个空间作为约定
  5. 什么是空出一个空间做约定?

看完以上属性的定义,我们来先思考以下几个公式实现环形数组队列的原因:

  • 当队列已满的条件公式:(rear + 1) % maxSize == front
  • 当队列为空的条件公式:rear == front
  • 队列中有效的成员的个数条件公式:(rear + maxSize - front) % maxSize

看完以上公式,我想现在的你恐怕已经开始陷入迷茫当中了,为什么这样定义类属性就可以实现环形数组队列?

请别着急,我们将逐一进行图解分析!

三、环形数组队列类属性的含义图解及代码实现

根据以上属性定义,较单向数组队列来说,仅仅只是队列前后指针标识初始位置发生变化(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);
    }
}

通过以上环形数组队列类的设计,就可创建一个如下的环形数组队列对象。(当然,目前还没有实现环形的作用)

image-20230325211545371

②判断环形数组队列是否为空

当队列为空的条件公式:rear == front

公式图解:

当队列前后指针相等时环形数组队列为空,这一点和单向数组队列没有什么区别。

这一点也很好理解,即从rear=front=0开始,增加第一个队列成员时,rear要向前移一位,此时front指向该队列成员。

image-20230325215616430

当我们拿取Member1后,front向后移动就会再次和rear相等,此时队列为空。

image-20230325225739449

代码实现:

/**
 * 判断环形队列是否为空
 *
 * @return
 */
public boolean isEmpty() {
    return front == rear; // 队列头部指针==队列尾部指针,说明队列为空
}

③预留一个空间作为约定的作用

在判断队列是否为满之前,我们先来理解一下上文所说的预留一个数组空间做约定的含义。

我们可以通过如下我从网上获取的分析图片来理解一下为什么要存在一个数组空间作为约定。

从图中我们可以看出,如果我们不去预留一个空间做约定的话,rear指针继续向前装队列成员,以至于rear指针与front指针相等,但是此刻环形数组却是满的状态,无法通过rear==front来判断环形队列到底是空还是满的状态,所以要牺牲一个数组空间作为约定,让rear在向后移的过程中判断是否将要和front相遇,来确定是否停止添加队列成员。所以也解释了为什么(maxSize - 1)作为该环形队列数组的最大存储容量。

image-20230325232233907

④判断环形数组队列是否为满

当队列已满的条件公式:(rear + 1) % maxSize == front

公式图解:

通过上述说明我们知道,环形队列的实现需要牺牲一个数组空间作为约定。我们知道如果环形队列满了,那么rear指针和front之间的距离就只差一个约定空间就可以实现rear==front,那这样的话我们可以设想如下两种环形数组队列满了的情况。

情况一:rear指针在front指针之后

image-20230326000254234

即为rear绕到了front的后面,在这种情况下,rear + 1 == front 等价于【(rear + 1) % maxSize == front】即能证明环形队列已满。

情况二:rear指针在front指针之前

image-20230325235822624

在这种情况下,我们发现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之前

image-20230326004457031

情况二:rear在front之后

image-20230326005806093

代码实现:

/**
 * 求出当前队列有效成员的个数
 *
 * @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
程序退出

五、实验总结

从上述整体实验中可以看出,环形数组队列类的设计,完全呈现出了队列存储数据先进先出的特点。同时实现数组空间复用的效果!

posted @ 2023-04-08 13:49  zhao-XH  阅读(309)  评论(0)    收藏  举报