1 基本概念
队列是一种先进先出(First in First Out)的线性表,简称FIFO。队列中,只允许在一端进行插入操作,而在另一端进行删除操作。允许插入的一端称为队尾,允许删除的一端称为队头。
如下图所示,假设队列q=(a1,a2,…,an),那么a1就是队头元素,而an是队尾元素。这样删除元素时,总是从a1开始,而插入时,总是在队列尾部插入。
2 数组模拟简单队列
队列本身是有序列表,若使用数组来存储队列的数据,则队列数组的声明如下图所示,其中 maxSize是该队列的最大容量。因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及 rear分别记录队列前后端的下标,front 会随着数据出队列而改变,而rear则是随着数据入队列而改变,如下图所示:
上图中,变量front与rear的初始值均为0,这表明front与rear相等时,队列为空。当rear==MaxSize成立时,队列为满。需要注意的是,front指向队列头部元素位置,而rear指向队列尾部后一个元素的位置。
简单队列的java实现
package com.victor.queue;
import java.util.Scanner;
//数组模拟简单队列
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue aq = new ArrayQueue(3);
char key = ' '; //接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单栏
while(loop){
System.out.println("s(show): 打印队列");
System.out.println("a(add): 入队列");
System.out.println("g(get): 出队列");
System.out.println("h(head): 打印队头");
System.out.println("e(exit): 退出程序");
key = scanner.next().charAt(0);
switch (key) {
case 's':
aq.showQueue();
break;
case 'a': //入队列
try {
System.out.println("请输入一个整数");
int value = scanner.nextInt();
aq.addQueue(value); //最好判断一下value是不是整数
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'g': //出队列
try {
int res = aq.getQueue();
System.out.printf("出队列的整数为%d
", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage()); //输出getQueue()方法中定义好的异常信息
}
break;
case 'h': //打印队头
try {
int res = aq.headQueue();
System.out.printf("队列头的整数为%d
", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'e': //退出
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
//使用数组模拟队列,ArrayQueue类
class ArrayQueue{
private int maxSize; //数组的最大容量
private int front; //队列头
private int rear; //队列尾,指向队列尾部后一个元素的位置
private int[] arr; //该数组用于存放数据,模拟队列
//队列的构造方法
public ArrayQueue(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
front = 0; //队列头部
rear = 0; //队列尾部,指向队列尾部后一个元素的位置,front=rear时队列为空
}
//判断队列是否为满队列
public boolean isFull(){
return rear == maxSize;
}
//判断队列是否为空队列
public boolean isEmpty(){
return rear == front;
}
//入队列
public void addQueue(int n){
if(isFull()){
throw new RuntimeException("队列满,不能添加数据了");
}
arr[rear] = n;
rear++; //rear后移
}
//出队列
public int getQueue(){
if(isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能出队列了");
}
int value = arr[front];
front++; //front后移
return value;
}
//打印队列
public void showQueue(){
if(isEmpty()){
System.out.println("队列空");
return;
}
// 打印队列中的元素,不是打印数组中的所有元素
for (int i = front; i < rear; i++){
System.out.printf("arr[%d]=%d
",i, arr[i]); //格式化输出
}
}
//打印队头,不是出队列
public int headQueue(){
if (isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能打印头");
}
return arr[front];
}
}
3 数组模拟循环队列
如下图a所示,当front指向下标为2的数组位置,rear指向下标为4的数组位置时,若向队列尾部再添加一个元素(a_{5}),则rear指针会越界,如下图b所示。但此时队列前部仍然有空间可以存储,所以简单队列的弊端就是不能充分利用数组空间。
为了解决数组空间不够用的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。比如上图b中,我们把rear置为0即可解决问题。
我们只需对前面的数组模拟简单队列进行优化,即可充分利用数组空间。 因此将数组看做是一个环形的,通过对front和rear指针取模实现指针循环。以下图为例进行说明:
变化如下:
-
front变量:指向队列的第一个元素,初始值为0。移动情况为front = (front + 1)% maxSize。
-
rear变量:指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定,rear的初始值为0。移动情况为rear = (rear + 1)% maxSize。
-
当队列满时,条件是(rear +1)% maxSize == front成立。因为空出了一个位置,故需要rear+1;
-
当队列为空时,rear == front成立;
-
队列中有效数据的个数:这里有两种情况,分别讨论:
(1)当front < rear 时,如下图,队列中的有效数据个数 = rear - front,这里rear-front = 2。
(2)当front > rear 时,如下图,队列中的有效数据个数=front右边的数据个数+rear左边的数据个数,又有:
- front右边的数据个数 = maxSize - front,这里maxSize - front = 3,maxSzie为数组的最大容量;
- rear左边的数据个数 = rear - 0 = rear,这里为0;
所以队列中的有效数据个数 = rear - front + maxSize
综合两种情况可得:
队列中的有效数据个数 = (rear - front + maxSize) % maxSize
循环队列的java实现
package com.victor.queue;
import java.util.Scanner;
//数组模拟循环队列
public class CircleArrayQueueDemo {
public static void main(String[] args) {
CircleQueue cq = new CircleQueue(4); //有效数据3个
char key = ' '; //接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单栏
while(loop){
System.out.println("s(show): 打印队列");
System.out.println("a(add): 入队列");
System.out.println("g(get): 出队列");
System.out.println("h(head): 打印队头");
System.out.println("e(exit): 退出程序");
key = scanner.next().charAt(0);
switch (key) {
case 's':
cq.showQueue();
break;
case 'a': //入队列
try {
System.out.println("请输入一个整数");
int value = scanner.nextInt();
cq.addQueue(value); //最好判断一下value是不是整数
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'g': //出队列
try {
int res = cq.getQueue();
System.out.printf("出队列的整数为%d
", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage()); //输出getQueue()方法中定义好的异常信息
}
break;
case 'h': //打印队头
try {
int res = cq.headQueue();
System.out.printf("队列头的整数为%d
", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case 'e': //退出
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
//使用数组模拟循环队列,CircleQueue类
class CircleQueue{
private int maxSize; //数组的最大容量
private int front; //队列头
private int rear; //队列尾,指向队列尾部后一个元素的位置
private int[] arr; //该数组用于存放数据,模拟队列
//队列的构造方法
public CircleQueue(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
front = 0; //队列头部
rear = 0; //队列尾部,指向队列尾部后一个元素的位置,front=rear时队列为空
}
//判断队列是否为满队列,因为预留了一个空间,所以要rear+1
public boolean isFull(){
return front == (rear + 1) % maxSize;
}
//判断队列是否为空队列,跟简单队列的条件一样
public boolean isEmpty(){
return rear == front;
}
//入队列
public void addQueue(int n){
if(isFull()){
throw new RuntimeException("队列满,不能添加数据了");
}
arr[rear] = n;
rear = (rear + 1) % maxSize; //rear要循环
}
//出队列
public int getQueue(){
if(isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能出队列了");
}
int value = arr[front];
front = (front + 1) % maxSize; //front要循环
return value;
}
//打印队列
public void showQueue(){
if(isEmpty()){
System.out.println("队列空");
return;
}
// 打印队列中的有效元素,注意这里i的边界条件,size()方法见下面
for (int i = front; i < front + size(); i++){
int index = i % maxSize; //index为循环后的下标
System.out.printf("arr[%d]=%d
",index, arr[index]); //格式化输出
}
}
//打印队头,不是出队列
public int headQueue(){
if (isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能打印头");
}
return arr[front];
}
//求出当前队列的有效数据个数
public int size(){
return (rear - front + maxSize) % maxSize;
}
}
reference
韩顺平数据结构
大话数据结构