一、实际案例
按照任务产生的顺序完成它们的策略我们每天都会遇到:在商场门前排队的消费者、在收费站前排队的汽车或是计算机中等待处理的任务。任何服务型策略的基本原则就是公平。公平大多数人的想法应该优先服务等待最久的人,这正是先进先出(First In First Out.,FIFO)
策略的准则。
二、基本介绍
2.1 队列定义
- 队列是一个有序列表,可以用数组或是链表来实现
- 遵循先进先出的原则,即先存储队列的数据要先被取出
2.2 图示
空队列:
进队列:
满队列
出队列
2.3 数组模拟
- 声明队列深度
maxSize
,即数据的长度 - 队列的输入输出完全依赖头
(head)
尾(tail)
指针控制,其中tail
随着数据入队列而增加,head
随着数据出队列而增加。初始化head = tail = -1
- 队列空
head = tail
- 队列满
tail = maxSize - 1
2.4 代码实现
package 队列;
/**
* Project: data structure
* Package: 队列
* Version: 1.0
* Author: wjun
* <p>
* Description:
* Created by hc on 2021/01/28 11:28
* © 1996 - 2021 Zhejiang Hong Cheng Computer Systems Co., Ltd.
*/
// 使用数组模拟队列
public class Queue {
private final int maxSize; //数组最大容量
private final int[] queue; //存储数据
private int head; //指向队列头
private int tail; //指向队列尾
//创建队列
public Queue(int maxSize) {
this.maxSize = maxSize;
queue = new int[maxSize];
head = -1;
tail = -1;
}
//判断队列是否满
public boolean isFull() {
return tail == maxSize - 1;
}
//判断队列是否空
public boolean isEmpty() {
return head == tail;
}
//添加数据,进队列
public void add(int n) {
//判断队列是否满
if (isFull()) {
System.out.println("队列满,无法添加数据");
return;
}
tail++; //尾指针后移
queue[tail] = n;
}
//获取数据,出队列
public int get() {
//判断队列书否空
if (isEmpty()) {
throw new RuntimeException("队列空,无法取出数据");
}
head++; //头指针后移
return queue[head];
}
//显示队列所有数据
public void show() {
if (isEmpty()) {
System.out.println("队列空,没有数据");
return;
}
for (int i = 0; i < queue.length; i++) {
System.out.printf("arr[%d] = %d\n", i, queue[i]);
}
}
//显示队列头数据
public int head() {
if (isEmpty()) {
throw new RuntimeException("队列空,无法取出数据");
}
return queue[head + 1];
}
}
演示代码:
package 队列;
import java.util.Scanner;
/**
* Project: data structure
* Package: 队列
* Version: 1.0
* Author: wjun
* <p>
* Description: 使用数组实现
* Created by hc on 2021/01/28 10:57
* © 1996 - 2021 Zhejiang Hong Cheng Computer Systems Co., Ltd.
*/
public class ArrayQueue {
public static void main(String[] args) {
Queue queue = new Queue(3);
Scanner scanner = new Scanner(System.in);
char key = ' ';//获取用户输入
while (true) {
System.out.println("s(show):显示队列 ");
System.out.println("e(exit):退出程序 ");
System.out.println("h(head):获取头数据");
System.out.println("a(add): 添加数据 ");
System.out.println("g(get): 获取数据 ");
System.out.print(">> ");
key = scanner.next().charAt(0);
switch (key) {
case 's':
queue.show();
break;
case 'e':
System.exit(-1);
break;
case 'h':
try {
int e1 = queue.head();
System.out.println("数据:" + e1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'a':
System.out.print("输入一个数");
queue.add(scanner.nextInt());
break;
case 'g':
try {
int e2 = queue.get();
System.out.println("数据:" + e2);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
}
}
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> a
输入一个数10
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> a
输入一个数20
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> a
输入一个数30
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> a
输入一个数40
队列满,无法添加数据
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> g
数据:10
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> g
数据:20
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> g
数据:30
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>> g
队列空,无法取出数据
s(show):显示队列
e(exit):退出程序
h(head):获取头数据
a(add): 添加数据
g(get): 获取数据
>>
2.5 问题
- 队列无法重复使用,一次性队列,当
tail = maxSize - 1
宣告结束。 - 薛定谔队列:当不断的进队列使队列满,随后不断出队列使队列空,这个时候这个队列即空又满。
三、环形队列
环形队列可以解决上述问题,让队列空间可以重复使用。这就要求head
和tail
可以循环取[0,maxSize]中的值,可以使用取模来实现。
3.1 图示
3.2 修正
head
指向队列第一个数据,tail
指向队列最后一个数据的后一个,初始化head = tail = 0
- 队列空
tail = head
- 为避免出现薛定谔队列,需要预留一个空间,保证除初始化队列和队列空时两指针指向同一个位置外不会重合,因此队列满
(tail + 1) % head = maxSize
问题:如何确定队列中元素个数?
以上述环形队列为例,有如下情况maxSize = 8
head | tail | num |
---|---|---|
0 | 1 | 1 |
1 | 7 | 6 |
3 | 0 | 5 |
可以看出规律
若tail > head => num = tail - head
若tail < head => num = maxSize - head
可以通过一个算式来统一num = (tail + maxSize - head) % maxSize
3.3 代码实现
package 队列;
/**
* Project: data structure
* Package: 队列
* Version: 1.0
* Author: wjun
* <p>
* Description: 环形队列
* Created by hc on 2021/01/28 14:15
* © 1996 - 2021 Zhejiang Hong Cheng Computer Systems Co., Ltd.
*/
/*
head 指向队列第一个元素 初始值0
tail 指向队列最后一个元素的后一个位置,初始值0
队列空:tail = head
队列满:(tail + 1) % maxSize = head
*/
public class CircularQueue {
private final int maxSize;
private final int[] queue;
private int head;
private int tail;
public CircularQueue(int maxSize) {
// 为什么+1?
// 上述分析为避免薛定谔队列,需要预留一个空位
// 因此在初始化数组是悄悄给既定大小加1
// 即悄悄多创建一个弥补预留空位
this.maxSize = maxSize + 1;
head = 0;
tail = 0;
queue = new int[maxSize + 1];
}
// 判断队列是否满
public boolean isFull() {
return (tail + 1) % maxSize == head;
}
// 判断队列是否空
public boolean isEmpty() {
return tail == head;
}
// 添加数据,进队列
public void add(int n) {
if (isFull()) {
System.out.println("队列满,无法添加数据");
return;
}
queue[tail] = n;
// 注意需要取模
tail = (tail + 1) % maxSize;
}
// 获取数据,出队列
public int get() {
if (isEmpty()) {
throw new RuntimeException("队列空,无法取出数据");
}
int tmp = queue[head];
// 注意需要取模
head = (head + 1) % maxSize;
return tmp;
}
// 显示队列所有数据
public void show() {
if (isEmpty()) {
System.out.println("队列空,无法取出数据");
return;
}
for (int i = head; i < head + size(); i++) {
// 注意需要取模
System.out.printf("queue[%d] = %d\n", i, queue[i % maxSize]);
}
}
// 查看队列头
public int head() {
if (isEmpty()) {
throw new RuntimeException("队列空,无法取出数据");
}
return queue[head];
}
// 获取队列元素个数
private int size() {
return (tail + maxSize - head) % maxSize;
}
}
演示参照上面的代码…