应用场景
银行、医院等场所的叫号系统等,先拿到号码的人先被服务,后拿到号码的人后被服务,这个时候就用到了队列
队列
队列是一个有序列表,可以用数组或是链表来实现
队列遵循先入先出的原则:即先存入队列的数据先取出,后存入的数据后取出
数组模拟队列示意图及思路:
- 其中MaxSize表示该队列的最大容量
- 由于队列的输出和输入分别由前后端来处理,因此需要两个变量来记录队列前后端的下标,其中 front 记录头数据的前一个位置的下标,rear 记录尾数据的下标,添加取出数据前都先变动指针
- 添加数据时:
- 将尾指针往后移一位:rear+1。当 front ==rear 时队列为空
- 若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。当rear ==maxSize-1 时队列是满的
- 取出数据时:将头指针后移一位
数组模拟队列代码实现
import java.util.Scanner;
public class TestQueueArray {
public static void main(String[] args) {
boolean loop = true;
//创建一个容量为3的模拟队列
QueueArray queue = new QueueArray(3);
Scanner scan = new Scanner(System.in);
while(loop) {
System.out.println(""
+ "e(exit):退出系统\n"
+ "a(add):向队列中添加数据\n"
+ "g(get):从队列中取出数据\n"
+ "h(head):查看头数据\n"
+ "s(show):查看队列所有数据");
System.out.print("请输入您的指令:");
String key = scan.next();
switch (key) {
case "e": //退出系统
loop = false;
scan.close();
break;
case "a": //向队列中添加数据
System.out.print("请输入一个数据:");
int value = scan.nextInt();
queue.addQueue(value);
break;
case "g": //从队列中取出数据
try {
System.out.printf("取出的数据为:%d\n",queue.getQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "h": //查看头数据(不是取出)
try {
System.out.printf("头数据为:%d\n",queue.headQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "s": //查看队列中所有的数据(不是取出)
try {
queue.showQueue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("请检查您的指令是否正确!");
break;
}
}
System.out.println("退出程序!");
}
}
//用数组模拟一个队列
class QueueArray {
private int maxSize;// 标记数组队列的最大容量
private int front; // 标记数组队列的头数据前一个位置的下标
private int rear; // 标记数组队列的尾数据的下标
private int[] arr; //声明一个数组作为模拟队列的容器
public QueueArray(int maxSize) {
this.maxSize = maxSize;
this.front = -1;
this.rear = -1;
this.arr = new int[this.maxSize];
}
// 判断数组队列是否已满
public boolean isFull() {
return this.rear == this.maxSize-1;
}
// 判断数组队列是否为空
public boolean isEmpty() {
return this.rear == this.front;
}
// 向数组队列里添加数据--入队
public void addQueue(int data) {
if (isFull()) {
System.out.println("数组队列满了,不能添加数据");
return;
}
this.rear++;
this.arr[rear] = data;
}
//从数组队列里读取数据--出队
public int getQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
this.front ++;
return this.arr[this.front];
}
//获取数组队列的全部数据
public void showQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
for (int i = this.front+1; i < this.rear+1; i++) {
System.out.printf("arr[%d] = %d\n",i,arr[i]);
}
}
//获取数组队列的头数据
public int headQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
return this.arr[this.front+1];
}
}
问题:当依次执行指令a、g后再执行s可以发现提示"数组队列是空的",理论上还可以存入3个数据,但是再执行两次指令a后再执行指令a,就会出现下面的提示:
原因:目前数组使用一次就不能用, 没有达到复用的效果
解决办法:将这个数组使用算法,改进成一个环形的队列 (取模:%)
数组模拟队列示意图及思路:
思路如下:
- front 就指向队列的第一个元素,front 的初始值为0
- rear 指向队列的最后一个元素的后一个位置,空出一个空间做为约定,rear 的初始值为0
- 当队列满时,条件是 (rear + 1) % maxSize == front
- 当队列为空时,条件是 rear == front
- 队列中有效数据的个数为 (rear + maxSize - front) % maxSize
- 将front与rear的增长设置为以下形式:front/rear=(front/rear + 1)%maxSize
数组模拟环形队列代码实现
import java.util.Scanner;
public class TestCircleQueueArray {
public static void main(String[] args) {
boolean loop = true;
CircleQueueArray queue = new CircleQueueArray(4);
Scanner scan = new Scanner(System.in);
while(loop) {
System.out.println(""
+ "e(exit):退出系统\n"
+ "a(add):向队列中添加数据\n"
+ "g(get):从队列中取出数据\n"
+ "h(head):查看头数据\n"
+ "s(show):查看队列所有数据");
System.out.print("请输入您的指令:");
String key = scan.next();
switch (key) {
case "e":
loop = false;
scan.close();
break;
case "a":
System.out.print("请输入一个数据:");
int value = scan.nextInt();
queue.addQueue(value);
break;
case "g":
try {
System.out.printf("取出的数据为:%d\n",queue.getQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "h":
try {
System.out.printf("头数据为:%d\n",queue.headQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "s":
try {
queue.showQueue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("请检查您的指令是否正确!");
break;
}
}
System.out.println("退出程序!");
}
}
//用数组模拟一个环形队列
class CircleQueueArray {
private int maxSize; // 标记数组队列的最大容量
private int front; // 标记数组队列的头数据的下标,初始值为0
private int rear; // 标记数组队列的尾数据后一个位置的下标,初始值为0
private int[] arr;
public CircleQueueArray(int maxSize) {
this.maxSize = maxSize;
this.arr = new int[this.maxSize];
}
// 判断数组队列是否已满
public boolean isFull() {
//实际上还有一个位置,但是留下来作为约定
return (this.rear + 1) % this.maxSize == this.front;
}
// 判断数组队列是否为空
public boolean isEmpty() {
return this.rear == this.front;
}
// 向数组队列里添加数据--入队
public void addQueue(int data) {
if (isFull()) {
System.out.println("数组队列满了,不能添加数据");
return;
}
//将传入的数据赋值给当前rear指向的位置
this.arr[rear] = data;
//将rear下移一位,使其始终指向尾数据的后一位
this.rear = (this.rear + 1) % this.maxSize;
}
//从数组队列里读取数据--出队
public int getQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
//记录当前front位置的数据
int value = this.arr[this.front];
//将front下移一位,使其始终指向环形队列的头数据
this.front = (this.front + 1) % this.maxSize;
return value;
}
//获取数组队列的全部数据
public void showQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
for (int i = this.front; i < this.front + size(); i ++) {
//i可能会大于队列的maxSize,因此对maxSize取模
System.out.printf("arr[%d] = %d\n", i%this.maxSize, arr[i%this.maxSize]);
}
}
//获取数组队列的有效数据个数
public int size() {
//对于非环形队列而言,rear-front即是有效数据个数
//而在环形队列中,rear的值可能比front小,因此需要加上maxSize并对maxSize取模
return (this.rear - this.front + this.maxSize) % this.maxSize;
}
//获取数组队列的头数据
public int headQueue() {
if(isEmpty()) {
throw new RuntimeException("数组队列是空的");
}
//该方法只是查看头数据,而非取出,因此不需要移动front指针
return this.arr[this.front];
}
}
如此一来就实现了环形队列,可以复用