Java:数组模拟队列以及优化数组模拟环形队列


前言

打卡:数组模拟队列和数组模拟环形队列


一、队列介绍以及数组模拟示意图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
要点:

  1. 队列遵守先进先出的原则——增加只能在尾部,删除只能在头部
  2. 开始头尾两个指针都在一个位置上——数组的初始位置0的前一个位置
  3. 加入一个数据,头不动,尾指针加1,删除一个数据尾指针不动,头加1——进行操作前都是先动指针的
  4. 队列的头数据指的是:当前时刻下,排在最前面的那个数据——假设有三个数据,进行了一次删除操作,头指针指的是0(删除操作要先加1),那么此刻的队列头数据是头指针front+1即1,就是第二个加入的数据了为头数据了(任何时刻头指针都在头部数据的前一个位置上)
  5. maxsize:最大容量,但是对应数组的话,数组的最大下标是maxsize(数组从0开始的)
  6. 队列空:头尾指针在一个位置上,队列满:尾指针到了数组的最大下标(maxsize-1)

二、数组模拟队列代码实现

代码如下(示例):

import java.util.Scanner;

public class ArrayQueueDemo {

    public static void main(String[] args) {
        //测试队列
        //创建一个队列
        ArrayQueue Queue = new ArrayQueue(3);//队列的大小为3对应数组最大下标为2从0开始
        char key; //= ' ';//接收用户输入
        Scanner in = 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 = in.next().charAt(0);//获取输入的第一个字符
            switch (key){
                case 's':
                    Queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数字");
                    int value = in.nextInt();
                    Queue.addQueue(value);
                    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'://退出
                    in.close();
                    loop = false;//循环会停,就不会出现菜单了
                    break;
                default:
                    break;//如果没有按要求输入,就会跳出语句,重新循环出菜单
            }
        }
        System.out.println("程序退出!");

    }

    //使用数组模拟队列-编写一个ArrayQueue类
        static class ArrayQueue{
        private int maxSize;//表示数组最大容量
        private int front;//队列头
        private int rear;//队列尾
        private int []arr;//该数组用于存放数据,模拟队列

        //创建队列的构造器
        public ArrayQueue(int arrMaxSize){//调用时带参数(队列的大小-数组的大小)
            maxSize = arrMaxSize;
            arr = new int[maxSize];
            front = -1;//指向队列头部数据的前一个位置
            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++;//加数据从尾部,必须先加,不然是-1指不到数组的初始位置
            arr[rear] = n;
        }

        //获取队列的数据,出队列
        public int getQueue(){//取出数据不用参数,因为只能按照头元素所指来顺序取出
          //判断队列是否为空
          if(isEmpty()){
              throw new RuntimeException("队列空,不能取数据");//不用return,抛出异常包含了这个操作
          }
          front++;//先后移,不然指不到数组的初始位置0
            return arr[front];//返回出去一个数据
        }

        //显示队列所有数据
        public void showQueue(){
            //遍历前,判断是否为空
            if(isEmpty()){
                System.out.println("队列空的,没有数据~");
            }
            for(int i=0;i<arr.length;i++){
                System.out.printf("arr[%d]=%d\n",i,arr[i]);
            }
        }

        //显示队列的头数据,注意不是取出数据
        public int headQueue(){
            //判断
            if(isEmpty()){
                throw new RuntimeException("队列空的,没有数据~~");
            }
            return arr[front+1];//就把它想成指着头部数据的前一个位置(-1,头部是0);取出数据后,指的也是头数据前一个位置(指的这个位置是没有数据的)
        }
    }
}

2.代码中的问题

Java的异常处理机制:异常的抛出和捕捉

  1. try:try块中主要放置可能会产生异常的代码块。如果执行try块里的业务逻辑代码时出现异常,系统会自动生成一个异常对象,该异常对象被提交给运行环境,这个过程被称为抛出(throw)异常。Java环境收到异常对象时,会寻找合适的catch块(在本方法或是调用方法),如果找不到,java运行环境就会终止,java程序将退出
  2. catch:catch块中放置当出现相应的异常类型时,程序需要执行的代码。当try中语句可能发生多个异常的时候可以由多个catch。
    那么这些异常类也有一些方法我们应该了解:
  3. getMessage();返回该异常的详细描述字符
  4. printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。(异常链)
  5. printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定的输出流
  6. getStackTrace():返回该异常的跟踪栈信息。
//显示队列的头数据,注意不是取出数据
        public int headQueue(){
            //判断
            if(isEmpty()){
                throw new RuntimeException("队列空的,没有数据~~");//双引号里面的就是异常的详细信息
            }
            return arr[front+1];//就把它想成指着头部数据的前一个位置(-1,头部是0);取出数据后,指的也是头数据前一个位置(指的这个位置是没有数据的)
        }
            }
 try {
                       int res = Queue.headQueue();
                        System.out.printf("队列头的数据是%d\n",res);//当执行这个程序时如果出现异常就执行catch语句块
                    } catch (Exception e) {
                        System.out.println(e.getMessage());//getMessage();返回该异常的详细描述字符
                    }

————————————————
原文链接:https://blog.csdn.net/zx64881926/article/details/52300271

3.测试中的问题

这个队列是一个一次性队列,指针移动后不能返回,当三个容量全部满载时,rear指针指到maxsize-1;取出全部数据后,front指针也指着maxsize-1,此时虽然是队列空的,但数组用完了,当添加数据时,rear指针移动不了,它到达数组上限了,删除数据也是如此。
它遍历的是数组的长度,除开加入的数据,默认的数值它也会遍历出来


三、数组模拟环形队列

请添加图片描述
请添加图片描述
要点:

  1. 可以用取余操作实现环形(数组的重复使用)
  2. 头指针和尾指针初始值为0,头指针就是指队列头元素,尾指针是指队列的最后一个数据的下一个位置
  3. 环形队列中会空出一个单元用来判满或者判空
  4. 因为判满的条件是(rear+1)%maxsize==front,就注定只能存放四个数据即rear最多指到4,数组 0 1 2 3 都有数据(在初始状态下),此时的预留单元是arr【4】
  5. 在第二轮回中,把满的队列数据全部取出(此时front和rear都等于4)再把队列加四个数据(此时队列满了,4 0 1 2 都有数据rear到3就队满了)那么此时的预留单元就是arr[3]了
  6. 可以画一个圆环,内分成等分格子,把所有操作模拟两遍就能理解了

2.代码实现

package DataStructures;

import java.util.Scanner;

public class ArrayQueueDemo {

    public static void main(String[] args) {
        //测试队列
        //创建一个环形队列
        ArrayQueue Queue = new ArrayQueue(5);
        char key; //= ' ';//接收用户输入
        Scanner in = 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 = in.next().charAt(0);//获取输入的第一个字符
            switch (key){
                case 's':
                    Queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数字");
                    int value = in.nextInt();
                    Queue.addQueue(value);
                    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'://退出
                    in.close();
                    loop = false;//循环会停,就不会出现菜单了
                    break;
                default:
                    break;//如果没有按要求输入,就会跳出语句,重新循环出菜单
            }
        }
        System.out.println("程序退出!");

    }

    //使用数组模拟队列-编写一个ArrayQueue类
        static class ArrayQueue{
        private int maxSize;//表示数组最大容量
        private int front;//指向队列的第一个元素,初始值为0
        private int rear;//指向队列的最后一个元素下一个位置,因为希望空出一个空间(这个空间是动态变化的的,可以是arr[4]也可以是arr[3]等)作约定,初始值也是0
        //反正它五个格子,0 1 2 3 4,只要满四个格子那么剩下的就是流出来的
        private int []arr;//该数组用于存放数据,模拟队列

        //创建队列的构造器
        public ArrayQueue(int arrMaxSize){//调用时带参数(队列的大小-数组的大小)
            maxSize = arrMaxSize;
            arr = new int[maxSize];
            front = 0;//默认是0,可以不写
            rear = 0;
        }

        //判断队列是否满
        public boolean isFull(){
            return (rear+1)%maxSize == front;//假设容量为5要留一个空间的话做判断的话,rear最多指到arr[4],队列满时队列的最后一个元素在arr[3]
        }

        //判断队列是否为空
        public boolean isEmpty(){
            return rear == front;//队列空的还是一样,因为初始值都0
        }

        //添加数据到队列
        public void addQueue(int n){//调用方法时要带参数(加入的值)
            //判断队列是否满
            if(isFull()){
                System.out.println("队列满,不能添加数据~");
                return;
            }
           arr[rear] = n;//因为初始值为0,是数组的初始位置直接加
            //将rear后移,这里考虑取余
            rear = (rear+1)%maxSize;
            //不大于最大容量的时候,就正常后移.当指针指到arr[4]队列就满了,1.如果此时取出一个元素
            //那么front就指着arr[1],队列此时是不满的,此时加一个数据的话,rear加1对maxsize取余等于零(又可以从头开始了)
            //此时队列是可以判满的,因为头是1尾也是1。?
            //如果依次取出当取出最后一个元素时头指着arr[3],如果再取一次头加一那就与尾指针相等(判空)
        }

        //获取队列的数据,出队列
        public int getQueue(){//取出数据不用参数,因为只能按照头元素所指来顺序取出
          //判断队列是否为空
          if(isEmpty()){
              throw new RuntimeException("队列空,不能取数据");//不用return,抛出异常包含了这个操作
          }
            //front指的是队列的第一个元素
            //先把front对应的值保存到一个临时变量中
            //再把头指针后移,也可能移动到数组最大下标,所以这里考虑取模
            //返回临时变量
            int value = arr[front];
            front = (front+1)%maxSize;
            //在尾指针和头指针都为4的判空情况下,此刻添加一个元素,尾就变成0从新开始了
            //再进行取出操作头也加一为5取余为0也从0从新开始(即取出的是刚刚加入的arr[0])
            return value;
        }

        //显示队列所有数据
        public void showQueue(){
            //遍历前,判断是否为空
            if(isEmpty()){
                System.out.println("队列空的,没有数据~");
            }
            //优化思路:从front开始遍历,遍历多少个元素,相当于只遍历有效数据
            for(int i=front;i<front+size();i++) {//因为rear指的那个位置没有数,所以不加等号
                System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
            }//假设把队列满的取空了,front和rear指的是4,再添加四个数据(因为是先加后移动指针的)那么此时rear指的是3队列是满的(根据计算)
             //直接相等就是空的,尾加1取余相等就队满

//            for(int i=0;i<arr.length;i++){//原来这个本来就不完善,存一个数据之后(队列中只有一个数据)
//            遍历整个数组他会把默认值0的数据也会遍历出来,也就是说只要不为空,不管队列有不有值都会显示出来
//                System.out.printf("arr[%d]=%d\n",i,arr[i]);
            }

            //求出当前队列有效数据的个数
        public int size() {
            return (rear+maxSize-front)%maxSize;//前辈的牛逼思想
        }

        //显示队列的头数据,注意不是取出数据
        public int headQueue(){
            //判断
            if(isEmpty()){
                throw new RuntimeException("队列空的,没有数据~~");
            }
            return arr[front];//指的就是队列头元素
        }
    }
}

总结

两种模拟方式都是在数组的基础上进行的,指针的操作具体看它的初始值(反正添加第一个数据时位置是从序号0开始的),环形队列因为两个指针的初始值都是0;所以先进行值得操作,再进行指针得操作。
环形可以用取余操作来进行


版权声明:本文为weixin_51753483原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。