队列是一个有序列表,可以用数组或是链表来实现。
遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
示意图:(使用数组模拟队列示意图)
队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 MaxSize 是该队 列的最大容量。
因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标, front 会随着数据输出而改变,而 rear 则是随着数据输入而改变,如上图所示。
代码实现及测试
public class ArrayQueue { private int[] arr;// 该数组用于存放数据 private int front; // 指向队列第一个数据前一个位置 private int rear; // 队列尾 private int MaxSize;// 队列的最大容量 public ArrayQueue(int ArrMaxSize){ MaxSize = ArrMaxSize; arr = new int[MaxSize]; front = rear = -1; } //判断队列是否为空 public boolean isEmpty(){ return front == rear; } //判断队列是否为满 public boolean isFull(){ return rear == MaxSize-1; } //入队 public void add(int data){ if (isFull()) throw new RuntimeException("队列满,无法加入该数据"+data); rear++; arr[rear] = data; System.out.println("添加成功"); } //显示队列的所有数据 public void showQueue(){ if (isEmpty()){ System.out.println("队列空的,没有数据"); return; } for (int i = front + 1; i < arr.length; i++) { //这里要从front+1的位置开始遍历,因为front指向的是出队的元素,我们要从下一个位置开始遍历 System.out.printf("arr[%d]=%d\n",i,arr[i]); } } //出队 public int getQueue(){ if (isEmpty()) throw new RuntimeException("队列为空,无法出队"); front++; System.out.println("已出队"); return arr[front]; } //显示队列头的元素 public int headQueue(){ if (isEmpty()){ throw new RuntimeException("队列空的, 没有数据"); } return arr[front + 1]; } } //使用数组模拟队列 public class ArrayQueueDemo { public static void main(String[] args) { ArrayQueue arrayQueue = new ArrayQueue(5); boolean flag = true; Scanner sc = new Scanner(System.in); char key = ' ';//接收用户输入 while (flag){ 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 = sc.next().charAt(0);//接收一个字符 switch (key){ case 's': arrayQueue.showQueue(); break; case 'a': System.out.println("请输入要入队的数据"); try { int value = sc.nextInt(); arrayQueue.add(value); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'g': try { int d = arrayQueue.getQueue(); System.out.println("取出的数据是: "+d); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'h': try { int h = arrayQueue.headQueue(); System.out.println("队列头部的数据是: "+h); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'e': sc.close(); flag = false; break; default: break; } System.out.println("======================"); } } }问题分析及优化思路
目前数组使用一次就不能用, 没有达到复用的效果
将这个数组使用算法,改进成一个环形的队列 取模:%
环形队列思路分析
对前面的数组模拟队列的优化,充分利用数组. 因此将数组看做是一个环形的。(通过取模的方式来实现即可)
front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
front 的初始值 = 0
rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.
rear 的初始值 = 0
当队列满时,条件是 (rear + 1) % maxSize == front 【满】
当队列为空的条件, rear == front 空
队列中有效的数据的个数 (rear + maxSize - front) % maxSize // rear = 1 front = 0
我们就可以在原来的队列上修改得到,一个环形队列。
代码实现及测试
public class CircleArrayQueue { private int[] arr;// 该数组用于存放数据 private int front; // 指向队列第一个数据的位置 private int rear; // 指向队列最后一个数据的后一个位置 private int MaxSize;// 队列的最大容量 public CircleArrayQueue(int ArrMaxSize){ MaxSize = ArrMaxSize; arr = new int[MaxSize]; // front = rear = 0; 因为类变量默认为0 , 所以可以不写这句 } //判断队列是否为空 public boolean isEmpty(){ return front == rear; } //判断队列是否为满 public boolean isFull(){ return (rear + 1) % MaxSize == front; /* 我个人的理解是: 如果这个队列是满的,那么由于空出来了一个位置作为约定,所以 1、 当rear在front上面(即比front的值大)的时候,rear一定是在数组的最后一个位置(下标为MaxSize-1),并且front在下表为0的位置, 所以rear+1的值就是MaxSize,于是(rear + 1) % MaxSize = 0,即rear+1的值等于数组的最大空间数,取模所得的结果也就是front的值; 2、 当rear在front下面(即比front的值小)的时候,rear的下一个位置一定是front(因为数组满的时候只可能空一个位置,也就是rear所在的位置), 并且rear+1也一定是小于MaxSize的,所以(rear + 1) % MaxSize = 它自己(rear + 1),也就是front 3、 因此,当队列满的时候,(rear + 1) % MaxSize == front是一定成立的。 */ } //入队 public void add(int data){ //要入列, 先得判断队列是否满了 if (isFull()) throw new RuntimeException("队列满,无法加入该数据"+data); arr[rear] = data; System.out.println("添加成功"); //注意rear不能是简单的++,要考虑超出数组范围的情况, 最终效果还是取到rear的下一位(是在一个循环里) rear = (rear + 1) % MaxSize; } //显示队列的所有数据 public void showQueue(){ //要遍历,先得判断队列是否为空 if (isEmpty()){ System.out.println("队列空的,没有数据"); return; } for (int i = front; i < front + size(); i++) { //因为i从front开始,如果rear比front的值小的话,就只能遍历到末尾,而0到rear就遍历不到,并且i的值会越界 //所以将i % MaxSize后, 即便是i大于MaxSize, 取完模就会回到前面的数值。 System.out.printf("arr[%d]=%d\n",i % MaxSize,arr[i % MaxSize]); } } //获取有效数据的个数 public int size(){ return (rear + MaxSize - front) % MaxSize; /* 我个人的理解: 1、 当rear的值比front大的时候, rear - front 就是数组中有效数据的个数(这是显而易见的,注意rear所在的位置是空的); 2、 当rear的值比front小的时候, MaxSize - front 即是从front到MaxSize-1位置的有效数据个数, 然后rear所在的下标位置 就是从队列头部到rear的有效数据个数, 因此rear + MaxSize - front就是两个的和,即总有效数据个数 3、 而对于1、中的情况, 将rear - front加上MaxSize再取模之后结果还是rear - front, 没有影响, 所以有效数据的个数就是 rear + MaxSize - front */ } //出队 public int getQueue(){ //要出队, 先判断队列是否为空 if (isEmpty()) throw new RuntimeException("队列为空,无法出队"); //先把要出队的数据用变量保存起来,因为front要变 int data = arr[front]; //注意front不能是简单的++,要考虑超出数组范围的情况,最终效果还是取到front的下一位(是在一个循环里) front = (front + 1) % MaxSize; System.out.println("已出队"); return data; } //显示队列头的元素 public int headQueue(){ //先判断队列是否为空 if (isEmpty()){ throw new RuntimeException("队列空的, 没有数据"); } return arr[front]; } } //测试用数组实现的循环队列 public class CircleArrayQueueDemo { public static void main(String[] args) { CircleArrayQueue arrayQueue = new CircleArrayQueue(5);//说明: 设置为5, 其队列的有效数据最大值为4 boolean flag= true; Scanner sc = new Scanner(System.in); char key = ' ';//接收用户输入 while (flag){ //输出一个菜单 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 = sc.next().charAt(0);//接收一个字符 switch (key){ case 's': arrayQueue.showQueue(); break; case 'a': System.out.println("请输入要入队的数据"); try { int value = sc.nextInt(); arrayQueue.add(value); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'g': try { int d = arrayQueue.getQueue(); System.out.println("取出的数据是: "+d); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'h': try { int h = arrayQueue.headQueue(); System.out.println("队列头部的数据是: "+h); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'e': sc.close(); flag = false; break; default: break; } System.out.println("======================"); } } }对取模原理的个人理解
假定给了一串数: 1 2 3 4 5 6 (注: 后面%6中的6 代表的意义是这串数字的个数 ,
所以说如果是0~5,依然是 % 6)
若 小于6的数x对6取模, 即x % 6, 结果依然是x。(即便是把1~6 改为 0~5依然成立)
若 大于6的数对6取模, 即x % 6, 结果就是比6大几就从前向后取几位
(e.g 7 % 6 = 1, 8 % 6 = 2)(即便是把1~6 改为 0~5依然成立)
这就对应了显示队列的所有数据的方法中取对应位置的值(i % MaxSize)
若 小于6的数x在 x加1 后对6取模, 即(x + 1) % 6, 结果依然是(x + 1),也就是紧挨着的下一位数。
(即便是把1~6 改为 0~5依然成立)
若 大于等于6的数x在 x加1 后对6取模, 即(x + 1) % 6, 结果就是比6大几就从前向后取几位
(e.g 7 % 6 = 1, 8 % 6 = 2)(即便是把1~6 改为 0~5依然成立)
这就对应了判断队列是否满了中的 (rear + 1) % MaxSize == front (若rear在最后一个位置,则(rear + 1)= 0,比6大 1, 从0开始取一位就是0, 即front所在位置)