什么是队列?
1)队列是一个有序列表,可以用数组或是链表来实现
2)遵循先入先出的原则。即先存入队列的数据要先取出,后存入队列的数据要后取出。(加数据是在队列的尾部加,取数据是在队列的首部取)
数组模拟队列
分析
(1)队列本身是一个有序列表,若使用数组的结构来存储队列的数据,则队列数的声明如下图,其中maxSize表示该队列的最大容量。
(2)因为队列的输出和输入是分别从此队列的前后端来处理的,因此需要两个变量 front 和 rear 分别记录队列前后端的下标,front 会随着数据输出二改变,rear 则是随着数据输入而改变。
存入队列的步骤
当我们将数据存入队列时称为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 需要注意。
运行程序进行演示
先显示队列查看队列中是否有数据
可以看到现在队列中是没有数据的,现在要往队列中存入数据
在存入一个数据10后,再次显示队列即可看到队列的第一个数是10,再次向队列中存入两个数据
可以看出此时队列已满,如再次向队列加入数据,则会提示队伍已满
从队列中取出两个数据后查看此队列头的数据是否为30
可以看到运算全部正确。
但是如果在取出两个数据的情况下还能否继续向队列中去存入数据呢?
再次存入数据发现就算是取出了数据的情况下依然不能向队列中存入数据,没有达到复用的效果,所以我们可以优化一下我们的程序,让它在取出数据后依然可以继续存入。
数组模拟环形队列
程序优化思路
(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进行显示查看
虽然队列长度为5,但因为空出了一个空间做约定,所以无法再向队列添加元素
再从队列中取出两个数据,并验证是否可以继续向队列中添加,来实现环形队列的效果
验证是否可以继续添加
添加后显示正确,所以已经构成了环形队列