生活中我们有很多排队的场景,比如下课,我们排队买午饭,上课时我们排队进电梯,在排队的时候,我们也会遵守一个先到先得的规则:
即我先到,我先排队,那么就是我先买饭/搭电梯
;那么这样一个排队的方式我们就可以称其为一个队列
,而我们所遵守的排队规则就是队列的先进先出
原则。
1、队列的介绍
- 队列是一个有序的列表,可以用
数组
或者联表
来实现; - 遵循
先进先出
原则。即:先存入队列的数据,要先取出。后存入的队列的后取出;
用数组来模拟队列
- 左边的这个队列中没有数据,因此
front(队列头)
和rear(队列尾)
都指向一个位置 - 中间的队列中存入了4个数据,队列头不变,队列尾向后移动4位;
(添加数据在队列的尾部加) - 右边的队列中由于取出了2个数据,队列尾不变,队列头向后移动2位;(取出数据在队列的首部取)
通过以上这个结构,我们来分析,实现一个队列需要的步骤和属性:
1.首先需要一个队列的声明:Queue;
2.确定队列的最大容量:maxSize;
3.标记队列的首部位置:front;这里取得队首得前一项为起始(随着数据的取出而改变)
4.标记队列的尾部位置:rear;(随着数据的添加而改变)
//先创建一个队列的对象
class ArrayQueue{
private int maxSize; //表示数组的最大容量
private int front; //队列头
private int rear; //队列尾
private int queue[]; //该数组用于模拟队列存放数据
//初始化队列
public ArrayQueue(int queueMaxSize){
maxSize = queueMaxSize;
queue = new int[maxSize];
front = -1; //表示队列的第一个数据的前一个位置
rear = -1; //表示队列最后一个数据的索引(-1就表示队列为空)
}
}
在队列中,还有几个常用的操作:
1.isEmpty判断队列是否为空
//判断队列是否为空
public boolean isEmpty(){
return front == rear;
}
显然当一个队列的头部和尾部都是同样的值,则这个队列就为空
2.isFull判断队列是否已满
//判断队列是否已满
public boolean isFull(){
return rear== maxSize - 1;
}
当一个队列的尾部索引rear等于队列最大容量Max-1时,表示队列已满
3.addQueue 将数据添加进队列,简称“入队操作”
//添加数据到队列(入队)
public void addQueue(int num){
//1.要先判断队列是否已满
if (isFull()){
System.out.println("队列已满,无法再进行添加操作!");
return;
}
//2.如果没有满,就对front进行后移一位,并将数据加入队列
queue[++rear] = num;
}
在对队列添加数据前,要判断队列是否已满,如果没有满再进行添加,添加后队列尾部索引rear要向后移动一位。
4.getQueue获取队列中的数据
//获取队列的数据(出队)
public int getQueue(){
//判断队列是否为空
if (isEmpty()){
//抛出异常
throw new RuntimeException("队列为空,无法取得数据");
//throw本身会直接导致代码return,因此不需要再写return语句
}
return queue[++front];
}
在获取队列数据前,需要判断队列中有没有数据,如果有数据,队列头部的数据出队,队列头指向front向后移动一位。
5.showHead显示队列头部数据
//显示队列头部数据
public void showHead(){
if (isEmpty()){
System.out.println("队列为空!");
return;
}
System.out.println(queue[front+1]); //由于front是从-1开始,要查看对头必须进行+1
}
即使是查看队列头,也要进行判空,查看队列是否有数据,如果有数据再进行展示。
6.showTail显示队列尾部数据
//显示队列尾部数据
public void showTail(){
if (isEmpty()){
System.out.println("队列为空");
return;
}
System.out.println(queue[rear]);
}
同样查看队列尾,也要进行判空,查看队列是否有数据,如果有数据再进行展示。
7.showQueue展示整个队列
//显示队列所有数据
public void showQueue(){
//判断队列是否为空
if (isEmpty()){
System.out.println("队列为空!");
return;
}
for (int i = front+1; i <= rear; i++) {
System.out.print(queue[i]+"\t");
}
System.out.println();
}
完成这个队列的基本操作和结构的编写,我们来进行测试:
public class Array_Queue {
public static void main(String[] args) {
//创建一个队列,并初始化
ArrayQueue arrayQueue = new ArrayQueue(10);
//查看队列是否为空
System.out.println("队列是否为空:"+arrayQueue.isEmpty());
//查看队列是否已满
System.out.println("队列是否已满:"+arrayQueue.isFull());
//向队列中添加值
for (int i = 0; i < 10; i++) {
System.out.println("向队列添加:"+i);
arrayQueue.addQueue(i);
}
//获取队列的数据
System.out.println("获取队首数据:"+arrayQueue.getQueue());
//显示队列头
System.out.print("显示队头数据:");
arrayQueue.showHead();
//显示队列尾
System.out.print("显示队尾数据:");
arrayQueue.showTail();
//显示整个队列
System.out.print("显示队列数据:");
arrayQueue.showQueue();
}
}
到此就基本完成了数组实现队列的功能。
但是我们进行测试,队列中添加满数据后,我们进行先进行取出操作,再进行添加,这时无法添加。
- 讲道理如果有条数据被取出的话,队列就应该多一个空位可以存放数据
这就是数组实现队列的弊端,
不可复用
2.数组实现环形队列
在之前我们使用数组模拟队列时,会发现数组使用一次之后无法再复用,这种方式虽然能够实现队列,但是利用效率低,用满即弃。我们要使用一种方式来提高数组的利用率。
- 思路:我们改进一下算法,将其改进成一个环形队列
1.将front变量的含义做一个调整:front初始化为第一个元素的索引,即0;
2.将rear变量的含义也做一个调整:rear指向队列的最后一个元素的后一个位置,也为0,因为希望空出一个空间作为约定;
3.当队列满时,条件是 (rear+1)%maxSize == front 【队列满】
4.当队列空时,条件是rear==front
5.队列中有效的数据个数:(rear+maxSize-front)%maxSize
首先,我们新建一个循环队列类:
class CircleArrayQueue{
private int maxSize; //表示数组的最大容量
private int front; //队列头:front初始化为第一个元素的索引,即0;
private int rear; //队列尾:rear指向队列的最后一个元素的后一个位置,初始化也为0,因为希望空出一个空间作为约定;
private int queue[]; //该数组用于模拟队列存放数据
//初始化循环队列
public CircleArrayQueue(int maxArraySize){
maxSize = maxArraySize;
queue = new int[maxSize];
front = 0;//作为第一个元素的索引
rear = 0; //作为约定位,即最后一个元素后面的一位空间
}
}
1、判断队列是否已满
//判断队列是否已满
public boolean isFull(){
/*
当队首为0时,队列的最后一个元素的索引+1就是maxSize,而maxSize%maxSize=front,即和队首相等,说明队列已满。
当队首为2时,队列的最后一个元素的索引因该是front-2,则rear = front-1,而(front-1+1)%maxSize = front,即和队首仍相等,说明队列也已满。
*/
return (rear+1) % maxSize == front;
}
2、判断队列是否为空
//判断队列是否为空
public boolean isEmpty(){
return front == rear;
}
3、入队操作
//添加数据到队列(入队)
public void addQueue(int num){
//1.要先判断队列是否已满
if (isFull()){
System.out.println("队列已满,无法再进行添加操作!");
return;
}
//因为rear指向的最后一个元素的后一个位置,所以直接取rear作为索引即可
queue[rear] = num;
//rear作为索引的值又添加了新的数据,就需要rear再向后移动,但是如果添加的是最后一个空间,rear就不能再进行++了,因此我们使用取模将rear指向第一个位置
rear = (rear+1) % maxSize;
}
4、出队操作
//获取队列的数据(出队)
public int getQueue(){
//判断队列是否为空
if (isEmpty()){
//抛出异常
throw new RuntimeException("队列为空,无法取得数据");
//throw本身会直接导致代码return,因此不需要再写return语句
}
/* 这里需要分析,我们在定义front的时候,本身就是指向第一个元素的
1.先把front指向的元素赋值给一个临时变量;(如果直接返回,front就没有办法进行移动操作,因为front也是不能一直进行++动作的)
2.将front后移;
3.将临时保存的变量返回;
*/
int result = queue[front];
front = (front+1) % maxSize;
return result;
}
5、显示队首数据
//显示队列头部数据
public void showHead(){
if (isEmpty()){
System.out.println("队列为空!");
return;
}
System.out.println(queue[front]);
}
6、显示队尾数据
//显示队列尾部数据
public void showTail(){
if (isEmpty()){
System.out.println("队列为空");
return;
}
System.out.println(queue[rear-1]);
}
7、显示队列所有数据
//显示队列所有数据
//求出当前队列中有效数据的个数
public int size(){
/*
rear = 1,front = 2
这时,空间内就应该有(1+5-2)%5=4条数据,索引分别为2,3,4,0(1号索引位被用来做为约定)
rear = 1,front = 0
这时,空间内就应该有(1+5-0)%5=1条数据,索引为0
*/
return (rear + maxSize - front) % maxSize;
}
public void showQueue(){
//判断队列是否为空
if (isEmpty()){
System.out.println("队列为空!");
return;
}
//思路:我们不能再单纯的直接打印数组里面的值,我们应该是从队列头开始遍历,遍历的次数应该是有效数据的个数
//
for (int i = front; i < front+size(); i++) {
//由于front + size()可能会大于maxSize,因此要对i进行取模
System.out.print(queue[i % maxSize]+"\t");
}
System.out.println();
}
测试代码
public static void main(String[] args) {
CircleArrayQueue queue = new CircleArrayQueue(5);
for (int i = 0; i < 4; i++) {
System.out.print("入队值:"+i+"\n");
queue.addQueue(i);
}
System.out.println("队列是否为空:"+queue.isEmpty());
System.out.println("队列是否已满:"+queue.isFull());
System.out.print("显示队列中所有的值:");
queue.showQueue();
System.out.print("显示队尾的值:");
queue.showTail();
System.out.println("获取队首的值"+queue.getQueue());
System.out.println("入队值:"+18);
queue.addQueue(18);
System.out.print("显示队列所有的值:");
queue.showQueue();
}
可以看到当队列满后,我们取出一个值,再进行添加,就可以向后排列了,不会出现再出现之前我们写普通队列那样数组不可复用的问题。
至于这里用到了取模,是因为,我们做循环队列的时候,是不能一直进行++的,因为队列的容量是有上限的,我们一直++势必会造成溢出,引发异常,因为是循环队列,顾名思义,肯定会循环往复,我们采用取模,就能做到这样一个循环往复的效果。