队列是满足先进先出,后进后出的一种结构,想象一个堵车的隧道,先进隧道的汽车在交通恢复后肯定第一个出隧道,后进隧道的汽车最后出隧道。
1.一个普通的队列分析
2 |
1 |
0 |
-1 |
如图所示,为一个队列,如果用数组实现,那么应该创建一个maxSize为3的数组,注意,“-1”是不存在的,为了便于理解加入了这一个格子。
一开始,有两个指针,一个叫start,一个叫end,他们两个在初始的时候都指向“-1”位置(实际上这是一个虚拟的格子),加入一个数据后,end指针加1指向0,而start依然保持在-1,加入第二个数据后,end指针继续加1指向1,start依然保持在-1;加入第三个数据,end指向2,start依然保持在-1;从这里可以看出,在插入数据时,start始终在-1,是不变的。按照此规则,队列满时条件为end=maxSize-1;
取出数据时,和上面类似,由先进先出原则,每取出一个数据,start就+1,而end保持不变,当数据全部取出时,start=maxSize-1;
2.环形队列
上面的队列有一个问题,那就是满了后,如果把一些数据取出后即使有空间也不能再向里面存数据了,为了解决这个问题,采用环形队列。
环形队列一般要牺牲一个空间,也就是说,如果你创建数组大小为8,则实际空间为7,其中一个空间需要被占用。
环形队列的初始值和上面有所不同,start和end一开始都位于0位置;
每加一个数据,end向上移动,也就是说,end始终位于最后一个数据的前面;
每取出一个数据,start向上移动,也就是说,start始终指向第一个数据;
注意,整个数组始终要保持一个位置是空的,这个空位置是算法的核心,填入maxsize-1个数据就定义为满。
这个算法如下:
满的时候,条件为:(end+1)%maxsize==start;
空的时候,条件为start==end;
有效数据的数量:number==(end-start+maxSize)%maxSize;
因为要做成“环形”,因此start和end就不能一直单调的+1,否则在加到maxSize-1的时候,再加就不行了,因为索引最大只有maxSize-1,环形的概念应该保证指针可以自动跳回0索引处,因此,还应该有:
添加一个数据,end=(end+1)%maxSize;
取出一个数据,first=(first+1)%maxSize;
这个算法讲解起来可能理解起来很抽象,建议找一张纸,画几个格子多演示几遍就理解了,记住关键是要预留一个位置!一定要预留一个位置!
2.1.环形队列的Java实现
代码实现如下,首先定义Queue类:
package 环形队列;
public class Queue {
private int maxSize;
private int front;
private int rear;
int[] arrQueue;
public Queue(int max) {
this.maxSize = max;
front = 0;
rear = 0;
arrQueue = new int[max];
}
// 判断队列是否满
public Boolean isFull() {
return (rear + 1) % maxSize == front;
}
// 判断队列是否空
public Boolean isEmpty() {
return front == rear;
}
//插入数据,如果已经满,需要直接return出该方法
public void addData(int data) {
if (isFull()) {
System.out.println("队列已满,无法插入");
return;
}
arrQueue[rear] = data;
rear = (rear + 1) % maxSize;
System.out.println("已添加数据:" + data);
}
//取出一个数据,如果空,需要直接报RuntimeException()错误,不能报Exception()错误然后Try Catch,这样会处理异常,程序依旧会向下运行
public int getData() {
if (isEmpty()) {
throw new RuntimeException("队列为空,没有数据");
}
int pop = arrQueue[front];
front = (front + 1) % maxSize;
return pop;
}
//获取有效数据个数
public int getSize() {
return (rear + maxSize - front) % maxSize;
}
//获取头数据
public int getFrontData() {
if (isEmpty()) {
throw new RuntimeException("队列空!");
}
return arrQueue[front];
}
//显示队列所有数据
public void showAllData() {
if (isEmpty()) {
System.out.println("队列空");
return;
}
for (int i = 0; i < getSize(); i++) {
System.out.printf("arr[%d] = %d\n", i % maxSize, arrQueue[i % maxSize]);
}
}
}
编写Demo类进行测试:
package 环形队列;
import java.util.Scanner;
public class DemoQueue {
public static void main(String[] args) {
Queue arrayQueue = new Queue(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("p(pop): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0);//获取用户输入的第一个字符
switch (key) {
case 's':
arrayQueue.showAllData();
break;
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
arrayQueue.addData(value);
break;
case 'p':
try {
int i = arrayQueue.getData();
System.out.printf("取出的数据是%d\n", i);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int i = arrayQueue.getFrontData();
System.out.printf("队列的头数据为%d\n", i);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
loop = false;
break;
default:
System.out.println("输入非法,请重新输入");
break;
}
}
System.out.println("程序退出~~");
}
}
犯的错误记录:在判断队列为空后把异常Try Catch了,这样的话异常就会被处理,程序依旧会向下运行,应该使用RuntimeException( )进行处理。