数组模拟队列时,会有不能复用的问题,这里使用环形队列来解决。
因为模拟的是环形队列,所以我们调整一下队列中各个属性的含义。
1.分析:
原来的非循环队列(非环形队列) ,在rear这个尾部指针到达rear==maxSize-1时,就不能再存放数据了,就算我们把数据给取出来,也只是在逻辑上把这个数据给取出来了,真实的情况是,这个数据取出来后,在队列中还有该数据,这个问题应该如何解决呢?答案是---环形队列。
所以我们要把这个队列给进行改进,把这个队列改成一个环形队列(循环队列),从而使该队列能够达到一个复用的效果。
1.环形队列的思想就是,当rear指针达到该队列的最大容量时,rear继续递增 从尾部到头部,从头部增加到尾部,这样一直循环存放数据。
2.但是如果是按照非循环队列(上篇数组模拟队列有说明)的条件rear==front来判断该队列是否存满,显然是无法判定的,因为刚开始没有数据的时候rear==front表明这个队列是空的。但是环形队列是一直递增的,当rear一直递增 增加到了头部也就是front的位置时,这个时候明明是有数据的,此时的rear也和front相等。所以按照肺循环队列的条件来是无法判定环形队列(循环队列)是否已存满的。
3.此时我们制定一个约定:我们牺牲环形队列中的一个空间 以便我们来判断该环形 队列是否已满。
-
因为我们预留了一个空间,所以rear这个属性调整为应该指向该队列最后一个元素的后一个的位置。也就是此时的rear=rear+1
-
rear的属性值的初始值为0
-
front这个属性调整为指向该队列头部的第一个元素。
-
front属性的初始值为0
-
因为rear是一直递增的,所以我们要对maxSize取余才可以得到rear的一个真实位置,所以此时判断队满的条件就是(rear+1)%maxSize==front 【队满】
-
队空条件是:rear==front。
-
我们这样分析时,队列中的有效数据个数就是 (rear+maxSize-front)%maxSize //如上图:rear=4,maxSize=5,front=0,有效数据个数就是:(4+5-0)%5==4个 上图中的有效数据个数正好是4个。
-
我们只需要在非循环队列(非环形队列)代码的基础上进行更改,就可以实现环形队列了。
package com.lzh.queue;
import java.util.Scanner;
public class CircleArrayQueue {
public static void main(String[] args) {
//测试--创建一个队列
CircleQueue circleQueue = new CircleQueue(4);
char key = ' ';//接收用户输入
Scanner scanner = 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=scanner.next().charAt(0); 要放在这个位置
key = scanner.next().charAt(0); //接收用户输入的第一个字符
switch (key) {
case 's':
circleQueue.showAll();
break;
case 'e' :
scanner.close();
loop = false;
System.out.println("程序退出");
break;
case 'a' :
System.out.println("请输入一个整数");
int a = scanner.nextInt();
circleQueue.addQueue(a);
break;
case 'g' :
try {
int res = circleQueue.getQueue();
System.out.println(res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'h' :
try{
int head = circleQueue.getHead();
System.out.println(head);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
}
}
}
}
//使用数组模拟环形队列
class CircleQueue{
//front和rear的初始值设置为0
private int maxSize;//数组的最大容量
//front 默认值为0
private int front;//指向队列的第一个元素
private int rear;//指向队列最后一个元素的后一个位置
private int[] arr;//该数组存放数据,用数组模拟队列
//创建一个构造方法
public CircleQueue(int arrayMaxSize){
maxSize = arrayMaxSize;
arr = new int[maxSize];
//front和rear不用赋值了,因为默认值就是0
}
//判断队列是否为空
public boolean isNull(){
return rear == front;
}
//判断队列是否已经存满了数据
public boolean isFull(){
return (rear+1)%maxSize == front;
}
//把数据加入队列
public void addQueue(int n){
//先判断这个队列是否已经满了
if(isFull()){
System.out.println("该队列已满,不能再添加数据了");
return;
}
//如果没满,则执行下面的代码
//因为rear是指向最后一个元素的后一个位置,所以这个地方的位置一定是空的,所以可以直接添加数据
arr[rear] = n;
//添加完之后,指针向后移,但是如果是环形队列的话,rear一直加可能会下标越界,所以这里用取模
rear=(rear+1)%maxSize;
}
//从队列中取出数据
public int getQueue(){
//先判断该队列是否为空
if(isNull()){
throw new RuntimeException("该队列为空,不能取出数据");
}
//因为front是指向该队列的第一个元素,所以可以直接取到
//用一个中间变量来接收取到的数据,然后再进行front后移,不然front无法后移
int value = arr[front];
//front也是一样,一直递增会造成下标越界,所以我们同样做一个取模计算
front = (front+1)%maxSize;
return value;
}
//显示头部数据
public int getHead(){
//先判断该队列是否为空
if(isNull()){
throw new RuntimeException("该队列为空,得不到头部数据");
}
return arr[front];
}
//显示所有数据
public void showAll(){
//先判断该队列是否为空
if (isNull()) {
System.out.println("该队列为空,取不到数据");
return;
}
//这里我们打印出所有数据,但是已经空的位置不用打印,所以我们让i=front,从第一个元素开始
//而这个front+size() 表示第一个元素的下标+多少个有效元素,取代了之前的i的范围
//比如front==1,size()==3 ,maxSize=5 则这个循环执行3次
for (int i = front;i<front+size();i++){
//这里为什么进行取模,因为i==front ,i<front+size()可能会下标越界,所以我们进行取模
System.out.printf("arr[%d]=%d\n", i%maxSize, arr[i%maxSize]);
}
}
//得到该队列有多少有效数据
public int size(){
return (rear+maxSize-front)%maxSize;
}
}