2.队列

本文详细介绍了如何使用数组实现队列,并分析了数组队列的弊端,即不可复用问题。接着提出并实现了环形队列的概念,通过调整front和rear的含义,解决了数组队列的空间利用率问题。环形队列在满和空的判断、入队、出队等操作上进行了优化,确保了数组空间的循环利用。通过示例代码展示了环形队列的完整功能,包括判断队列状态、数据操作和显示等。
摘要由CSDN通过智能技术生成

生活中我们有很多排队的场景,比如下课,我们排队买午饭,上课时我们排队进电梯,在排队的时候,我们也会遵守一个先到先得的规则:即我先到,我先排队,那么就是我先买饭/搭电梯;那么这样一个排队的方式我们就可以称其为一个队列,而我们所遵守的排队规则就是队列的先进先出原则。

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();
}

在这里插入图片描述

可以看到当队列满后,我们取出一个值,再进行添加,就可以向后排列了,不会出现再出现之前我们写普通队列那样数组不可复用的问题。

至于这里用到了取模,是因为,我们做循环队列的时候,是不能一直进行++的,因为队列的容量是有上限的,我们一直++势必会造成溢出,引发异常,因为是循环队列,顾名思义,肯定会循环往复,我们采用取模,就能做到这样一个循环往复的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值