队列Queue

队列Queue

1. 队列介绍

  • 队列是一个有序列表,可以用数组或者链表来实现,用数组实现就是顺序存储,用链表实现就是链式存储。二者之间的区别是:
    • 链式存储结构的内存地址不一定是连续的,而顺序存储结构的内存地址一定是连续的;
    • 链式存储适用于频繁地进行插入、删除以及更新元素时的情况,而顺序存储适用于频繁地查询时使用;
    • 顺序存储比链式存储更节约空间,因为链式存储结构每一个节点都有一个指针存储域;
    • 顺序存储支持随机存取,方便操作;
    • 但顺序存储再插入时花费更大的空间复杂度,而链式存储是索引后直接插入,因此在插入与删除时,链式存储比顺序存储更加方便、快捷。
  • 队列遵循先入先出的原则。

2. 数组模拟队列

数组模拟队列
  • 如上图是数组实现队列的声明,MaxSize表示该队列的最大容量,由于队列的输入与输出分别是从前后端处理,因此需要定义front和rear两个变量,分别记录队列前后端的下标,其中插入数据时front不变rear变化,输出数据时rear不变font变化(先进先出原则)。
  • front指向队列头的前一个位置,rear指向队列最后一个元素所在的位置
  • 以数据进队列为参考,addQueue主要执行两个步骤:
    • 先判断队列容量是否已满,只有当rear小于MaxSize-1(front和rear在初始定义时均为-1,数组下标从0开始)时才能将数据存入队列,rear==MaxSize-1时队列满无法存入数据;(数据出队列时则要判断队列是否为空,front==rear)
    • 数据进队列时将尾指针后移rear++。

3. 数组模拟队列代码实现

package com.datastructure;

import java.util.Scanner;

/**
 * @author SnkrGao
 * @create 2023-03-01 21:51
 */
public class ArrayQueueDemo { // 数组模拟队列

    public static void main(String args[]) {
        // 创建队列
        ArrayQueue queue = new ArrayQueue(5);
        // 写一个程序用于测试
        char key = ' '; // 接受用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        while (loop) {
            System.out.println("使用说明:");
            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):查看队列头的数据");
            System.out.println("-----------------------------");
            key = scanner.next().charAt(0);

            switch (key) {
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("请输入数据:");
                    int n = scanner.nextInt();
                    queue.addQueue(n);
                    break;
                case 'g':
                    try {
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是:%d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int res = queue.headQueue();
                        System.out.printf("队列头数据是:%d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
    }
}

// 队列构造类
class ArrayQueue {
    private int maxSize; // 定义队列的最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private int[] arr; // 用来模拟队列的数组,存放数据

    // 构造函数,创建队列
    public ArrayQueue(int arrMaxSize) {
        this.maxSize = arrMaxSize;
        this.front = -1; // 指向队列头的前一个位置
        this.rear = -1; // 指向队列尾部数据的位置
        this.arr = new int[arrMaxSize];
    }

    // 判断队列是否已满
    public boolean isFull() {
        return rear == maxSize - 1;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }

    // 往队列中添加数据
    public void addQueue(int n) {
        // 首先判断队列是否已满
        if (this.isFull()) {
            System.out.println("队列已满,无法添加数据!");
            return;
        }
        rear++; // rear后移
        arr[rear] = n;
    }

    // 获取队列数据
    public int getQueue() {
        // 首先判断队列是否为空
        if (this.isEmpty()) {
            // 通过异常抛出 null
            // 方法需要返回一个int类型参数,但不能直接return -1
            throw new RuntimeException("队列为空,无法取数据!");
        }
        front++; // front后移,指向新的队列头的前一个位置
        int result = arr[front]; // 从数组中取出的值
        arr[front] = 0; // 数组中原位置赋0

        return result;
    }

    // 显示队列的所有数据,此处显示的为所有在数组中存的数据,仅用于验证addQueue,不会随getQueue变化
    public void showQueue() {
        if (this.isEmpty()) {
            System.out.println("队列为空,无法显示数据!");
            return;
        }
        // 遍历数组
        for (int i = 0; i < arr.length; i++) {
            System.out.printf("arr[%d]=%d\n", i, arr[i]);
        }
    }

    // 显示队列头,而非取数据,用于验证getQueue
    public int headQueue() {
        if (this.isEmpty()) {
            throw new RuntimeException("队列为空,无法获取队列头!");
        }
        // 显示
        return arr[front+1];
    }
}

4. 此数组模拟队列存在的问题及优化方法

  1. 目前设计的用数组模拟的队列只能使用一次,在上例中体现为队列中数据全部取出后,front=rear=Maxsize-1,无法再存入和取出数据(因为数组只能使用一次),没有达到复用的效果
  2. 可以将此数组用算法进行改进,改成一个环形队列,取模%;或者也可以用两个栈来模拟队列(leetcode题)。

5. 数组模拟环形队列

数组模拟环形队列

一种实现思路:1. front变量进行调整:初始值改为front=0,改为就指向队列的第一个元素,即arr[front]就是队列的第一个元素;

  1. rear变量进行调整:初始值改为rear=0,改为指向队列最后一个元素的后一个位置,数组空出一个空间方便判断队列是否满或空(实现环形),即arr[rear-1]才是队列的最后一个元素,且数组大小为MaxSize队列最大长度为MaxSize-1
  2. 判断队列满的条件:(rear + 1) % MaxSize == front;
  3. 判断队列空的条件:rear == front;
  4. 当数据入队列时,rear需后移的同时考虑取模(环形),即rear = (rear +1) % MaxSize;出队列时同理,front = (front + 1) % MaxSize;(因为front和rear都是自增的但队列是环形的,可能出现MaxSize=5但rear自增到6的情况,因此需要进行取模运算);
  5. 队列中的有效数据个数为:(rear + MaxSize - front) % MaxSize。

6. 数组模拟环形队列代码实现

package com.datastructure;

import java.util.Scanner;

/**
 * @author SnkrGao
 * @create 2023-03-03 20:38
 */
public class circleArrayQueueDemo { // 数组模拟环形队列

    public static void main(String args[]) {
        // 创建队列,队列实际容量为4
        circleArrayQueue circlearrayqueue = new circleArrayQueue(5);
        // 写一个程序用于测试
        char key = ' ';
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;

        while (loop) {
            System.out.println("使用说明:");
            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):查看环形队列头的数据");
            System.out.println("--------------------------------");
            key = scanner.next().charAt(0);

            switch (key) {
                case 's' :
                    circlearrayqueue.showCircleQueue();
                    break;
                case 'a' :
                    System.out.println("请输入数据:");
                    int value = scanner.nextInt();
                    circlearrayqueue.addCircleQueue(value);
                    break;
                case 'g' :
                    try {
                        int result = circlearrayqueue.getCircleQueue();
                        System.out.println("从环形队列中取出的数据为:"+result);
                    } catch (Exception e) { // 输出捕获的异常信息
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h' :
                    try {
                        int result = circlearrayqueue.headCircleQueue();
                        System.out.println("环形队列的头数据为:"+result);
                    } catch (Exception e) { // 输出捕获的异常信息
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e' :
                    scanner.close();
                    loop = false;
                    System.out.println("程序进程结束,退出程序!");
                    break;
                default :
                    break;
            }
        }
    }
}

// 环形队列构造类
class circleArrayQueue {
    private int maxSize; // 定义数组的最大容量,队列的最大容量为maxSize-1
    private int front; // 指向队列头
    private int rear;  // 指向队列最后一个元素的后一个位置
    private int[] arr;

    // 构造方法,创建环形队列
    public circleArrayQueue(int arrMaxSize) {
        this.maxSize = arrMaxSize;
        this.arr = new int[arrMaxSize];
        // front和rear初始化时均为0,因此此处不需要重复对其赋值
    }

    // 判断环形队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }

    // 判断环形队列是否已满
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    // 往队列中添加数据
    public void addCircleQueue(int n) {
        if (isFull()) {
            System.out.println("环形队列已满,无法添加数据!");
            return;
        }
        arr[rear] = n;
        rear = (rear + 1) % maxSize;
    }

    // 从队列中取数据
    public int getCircleQueue() {
        if (isEmpty()) {
            throw new RuntimeException("环形队列为空,无法取数据!");
        }
        // 先定义一个临时变量value用于存储值
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }

    // 显示队列中的所有数据
    public void showCircleQueue() {
        if(isEmpty()) {
            System.out.println("环形队列为空,无法显示数据!");
            return;
        }
        // 遍历队列应从front开始,遍历长度为队列有效数据个数
        // size为环形队列的有效数据个数
        int size = getCircleQueueSize();
        for (int i = front; i < front+size; i++) {
            // 由于front+size可能数组越界,因此需要取模
            System.out.println("arr["+i%maxSize+"]="+arr[i%maxSize]);
        }
    }

    // 求出队列的有效数据个数
    public int getCircleQueueSize() {
        return (rear + maxSize - front) % maxSize;
    }

    // 显示队列头的数据
    public int headCircleQueue() {
        if (isEmpty()) {
            throw new RuntimeException("环形队列为空,无法获取环形队列头数据!");
        }
        return arr[front];
    }
}
posted @ 2023-03-05 13:05  GaoakaJie  阅读(88)  评论(0)    收藏  举报