【数据结构】—— 队列基础知识以及数组模拟队列的分析、演示及优化

简介: 【数据结构】—— 队列基础知识以及数组模拟队列的分析、演示及优化

什么是队列?


1)队列是一个有序列表,可以用数组或是链表来实现

2)遵循先入先出的原则。即先存入队列的数据要先取出,后存入队列的数据要后取出。(加数据是在队列的尾部加,取数据是在队列的首部取)


数组模拟队列

分析


(1)队列本身是一个有序列表,若使用数组的结构来存储队列的数据,则队列数的声明如下图,其中maxSize表示该队列的最大容量。


(2)因为队列的输出和输入是分别从此队列的前后端来处理的,因此需要两个变量 front 和 rear 分别记录队列前后端的下标,front 会随着数据输出二改变,rear 则是随着数据输入而改变。


54cf18a86bac422c84768a639919fc57.png


存入队列的步骤

当我们将数据存入队列时称为addQueue,addQueue的处理需要有两个步骤:

(1)将尾指针往后移,即rear + 1,当 front == rear 时,说明此时队列为空


(2)若尾指针 rear 小于队列的最大下标 maxSize - 1,则可以将数据存入 rear 所指的数组元素中,否则无法存入数据。即当 rae == maxSize - 1 时,说明该队列已满。

使用数组模拟队列—编写一个ArrayQueue类

class ArrayQueue {
    private int maxSize;//队列的长度,也就是最多能存储多少个数据
    private int front;//队列头
    private int rear;//队列尾
    private int[] arr;//用于存放数据,模拟队列
    //创建队列的构造器
    public ArrayQueue(int arrMaxSize) {
        maxSize = arrMaxSize;//将队列的长度赋值
        arr = new int[maxSize];;//创建长度为maxSize的数组
        front = -1;//指向队列的头部,分析出front是指向队列头的前一个位置
        rear = -1;//指向队列尾部,也就是指向队列尾的数据(即队列的最后一个数据)
    }
    //判断队列是否满
    public boolean isFull() {
        return rear == maxSize - 1;
    }
    //判断队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }
    //添加数据到队列
    public void addQueue(int n) {
        //判断队列是否为满
        if(isFull()) {
            System.out.println("队伍已满,无法加入队列");
            return;
        }
        rear++;//让rear后移
        arr[rear] = n;//添加数据到数组中
    }
    //获取队列的数据,出队列
    public int getQueue() {
        //判断队列是否为空
        if(isEmpty()) {
            //如果为空就抛出异常
            throw new RuntimeException("队列为空,不可以读取数据");
            //return //不需要进行返回,因为抛出异常就已经等于进行了返回
        }
        front++;//让front后移
        return arr[front];
    }
    //显示队列
    public void showQueue() {
        //先判断是否为空,为空就停止
        if(isEmpty()) {
            System.out.println("队列为空,无法输出");
            return;
        }
        //若队列不为空则遍历数组输出
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "  ");
        }
    }
    //显示队列的头数据,注意不是读取数据
    public int headQueue() {
        //判断是否为空
        if(isEmpty()) {
            throw new RuntimeException("队列为空,无法输出");
        }
        return arr[front + 1];
            //front + 1 是因为front是指向队列的头一个位置,所以要加一
    }

编写完ArrayQueue类后,还需要编写一个ArrayQueueDemo演示类,调用方法进行验证

编写ArrayQueueDemo类进行调用方法演示

import java.util.Scanner;
public class ArrayQueueDemo { // 队列
    public static void main(String[] args) {
        //创建队列进行测试
        ArrayQueue arrayQueue = new ArrayQueue(3);
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        while(loop) {
            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):退出程序");
            System.out.println("=====请输入要求=====");
            char key = scanner.next().charAt(0);//接收用户输入的一个字符
            switch(key) {
                case 's'://显示队列
                    arrayQueue.showQueue();
                    System.out.println();
                    break;
                case 'a'://添加数据到队列
                    System.out.println("请输入一个整数:");
                    int value = scanner.nextInt();
                    arrayQueue.addQueue(value);
                    break;
                case 'g'://取出数据
                    //取出数据时可能会遇到异常,因为有可能没有数据可以取出
                    //所以要使用异常处理机制try catch
                    try {
                        int res = arrayQueue.getQueue();
                        //如果getQueue()没有抛出异常,就会取出数据
                        System.out.println("取出的数据是:" + res);
                    } catch (Exception e) {
                        //如果getQueue()抛出异常,就会被catch抓住
                        //所以会在catch中输出异常信息
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h'://查看队列头的数据
                    try {
                        int res = arrayQueue.headQueue();
                        System.out.println("队列头的数据是:" + res);
                    } catch (Exception e) {
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e'://退出程序
                    scanner.close();//退出前先把scanner关闭,如不关闭可能会有异常
                    loop = false;//如果退出程序那么loop就为false,while循环就不会通过
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序已退出");
    }
}


这里面使用了一个异常处理机制 try catch 需要注意。

运行程序进行演示

先显示队列查看队列中是否有数据

59cb5180923f458fb8123c70a61d226b.png

可以看到现在队列中是没有数据的,现在要往队列中存入数据

eb1d7fb188864a9190ab4c8abb93403a.png

在存入一个数据10后,再次显示队列即可看到队列的第一个数是10,再次向队列中存入两个数据

bff4e95d03004624932dffa8921d40dc.png


可以看出此时队列已满,如再次向队列加入数据,则会提示队伍已满


4209703fb4854d048cb919f870456861.png

从队列中取出两个数据后查看此队列头的数据是否为30


e7f2943f99fb418cb5404bec54f2ad98.png


可以看到运算全部正确。

但是如果在取出两个数据的情况下还能否继续向队列中去存入数据呢?

262b81d7ddbd4a85952fa51e97ba4163.png


 再次存入数据发现就算是取出了数据的情况下依然不能向队列中存入数据,没有达到复用的效果,所以我们可以优化一下我们的程序,让它在取出数据后依然可以继续存入。


数组模拟环形队列


程序优化思路


(1)front 变量的含义进行一个调整:让 front 指向队列的第一个元素,也就是说 arr[front] 为队列的第一个元素,front 的初始值为0。


(2)rear 变量的含义做一个调整:让 rear 指向队列的最后一个元素的后一个位置,因为要空出一个空间来做约定,rear 的初始值为0.


(3)当队列为满时,条件为(rear + 1) % maxSize == front


       例如当 rear = 2 ,front = 0 时,maxSize - 1 = 2,maxSize = 3(因为下标从零开始),所以(2 + 1) % 3 == 0,所以队列已满。


(4)当队列为空时,条件为 rear == front


(5)分析完成后,该队列中有效数据的个数是 (rear + maxSize - front) % maxSize


       例如当 rear = 2,front =0,maxSize = 3时,(2 + 3 - 0) % 3 等于 2 ,说明该队列中有效数据的个数为两个。


使用数组模拟环形队列—编写一个CircleArrayQueue类

class CircleArrayQueue {
    private int maxSize;//队列的长度,也就是最多能存储多少个数据
    private int front;//队列头
    private int rear;//队列尾
    private int[] arr;//用于存放数据,模拟队列
    //创建队列的构造器
    public CircleArrayQueue(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        /*
            front 变量的含义进行一个调整:让 front 指向队列的第一个元素,
        也就是说 arr[front] 为队列的第一个元素,front 的初始值为0。
            rear 变量的含义做一个调整:让 rear 指向队列的最后一个元素的后一个位置,
        因为要空出一个空间来做约定,rear 的初始值为0.
         */
        //因为front 和 rear 默认为零,所以不用进行赋值
    }
    //判断队列是否满
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }
    //判断队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }
    //添加数据到队列
    public void addQueue(int n) {
        //判断队列是否为满
        if(isFull()) {
            System.out.println("队伍已满,无法加入队列");
            return;
        }
        arr[rear] = n;//添加数据到数组中
        //将 rear 后移一位,这里必须要考虑取模,因为rear很有可能会产生越界
        rear = (rear + 1) % maxSize;
    }
    //获取队列的数据,出队列
    public int getQueue() {
        //判断队列是否为空
        if(isEmpty()) {
            //如果为空就抛出异常
            throw new RuntimeException("队列为空,不可以读取数据");
            //return //不需要进行返回,因为抛出异常就已经等于进行了返回
        }
        /*
        这里需要分析出 front 是指向队列的第一个元素
        1.先把 front 对应的值保存到一个临时变量中
        (如果不把值保存到临时变量中,那么 front 就没有往后移的机会了)
        2.将 front 后移,并且考虑取模,因为front也有可能会产生越界
        3.将临时保存的变量返回
         */
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }
    //显示队列
    public void showQueue() {
        //先判断是否为空,为空就停止
        if(isEmpty()) {
            System.out.println("队列为空,无法输出");
            return;
        }
        //若队列不为空则遍历数组输出
        //思考,从front开始遍历,遍历了多少个元素
        for (int i = front; i < front + number(); i++) {
            System.out.print(arr[i % maxSize] + "  ");
            //因为 i 在运行时可能会超过数组的大小,所以要进行模除
        }
    }
    //求出当前队列有效数据的个数
    public int number() {
        return (rear + maxSize - front) % maxSize;
    }
    //显示队列的头数据,注意不是读取数据
    public int headQueue() {
        //判断是否为空
        if(isEmpty()) {
            throw new RuntimeException("队列为空,无法输出");
        }
        return arr[front];
        //front 不需要 +1 是因为 front 本身就指向队列的第一个元素
    }
}


编写CircleArrayQueueDemo类进行调用方法演示

import java.util.Scanner;
public class CircleArrayQueueDemo {
    public static void main(String[] args) {
        //创建环形队列进行测试
        CircleArrayQueue circleArrayQueue = new CircleArrayQueue(5);
        //因为要空出一个空间来做约定,所以队列长度为5,但最多只能存入4个元素
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        while(loop) {
            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):退出程序");
            System.out.println("=====请输入要求=====");
            char key = scanner.next().charAt(0);//接收用户输入的一个字符
            switch(key) {
                case 's'://显示队列
                    circleArrayQueue.showQueue();
                    System.out.println();
                    break;
                case 'a'://添加数据到队列
                    System.out.println("请输入一个整数:");
                    int value = scanner.nextInt();
                    circleArrayQueue.addQueue(value);
                    break;
                case 'g'://取出数据
                    //取出数据时可能会遇到异常,因为有可能没有数据可以取出
                    //所以要使用异常处理机制try catch
                    try {
                        int res = circleArrayQueue.getQueue();
                        //如果getQueue()没有抛出异常,就会取出数据
                        System.out.println("取出的数据是:" + res);
                    } catch (Exception e) {
                        //如果getQueue()抛出异常,就会被catch抓住
                        //所以会在catch中输出异常信息
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h'://查看队列头的数据
                    try {
                        int res = circleArrayQueue.headQueue();
                        System.out.println("队列头的数据是:" + res);
                    } catch (Exception e) {
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e'://退出程序
                    scanner.close();//退出前先把scanner关闭,如不关闭可能会有异常
                    loop = false;//如果退出程序那么loop就为false,while循环就不会通过
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序已退出");
    }
}


运行程序进行演示

先输入四个数据10、20、30、40进行显示查看


11793ba44c3b4b2d8a7714a726337d40.png

虽然队列长度为5,但因为空出了一个空间做约定,所以无法再向队列添加元素


ba34cfbd686747c7b9f454925a56d347.png


再从队列中取出两个数据,并验证是否可以继续向队列中添加,来实现环形队列的效果

194addfb4a0d4c59b63930fa4de0ca09.png

验证是否可以继续添加

50a742fca69b4f5e9cc5d7ad0fa40f22.png


添加后显示正确,所以已经构成了环形队列

相关文章
|
15天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
9天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
18天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
初步认识栈和队列
初步认识栈和队列
58 10
|
1月前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
16 0
|
1月前
探索数据结构:队列的的实现与应用
探索数据结构:队列的的实现与应用
|
1月前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
16 0
|
6天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
15 1
|
9天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。