数据结构——队列

本文探讨了如何使用数组创建队列,包括队列的初始化、添加和获取数据的问题。作者提出将队列设计为一个类,以解决首尾值的管理。在遇到满队列时,提出了环形队列的概念,通过将数组视为环形结构来避免假溢出问题,并详细解释了环形队列的实现思路,包括如何判断队列满、空及获取有效数据数量的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建队列、塞值和拿值

当我们创建一个LinkedList的时候,就可以用来模拟队列,因为该集合里有大量操作首尾元素的方法,之后就可以在该队列里进行数据的添加和获取。

但是当我们使用数组来实现时,如何创建一个队列呢?最大值怎么确定?首尾初始值怎么确定?

这一步骤中我就有了一些疑惑,如果我把创建队列、添加队列数据和删除队列数据分别用三个方法来表示,那么队列的首尾值该怎么跟随着队列呢?难道是要把它存储到创建的队列中?似乎不太合理,但是如果是每次使用队列的时候单独确定首尾值然后再传递的话那也显得不太合理,毕竟这个应该是队列的一个属性,咦?写到这里我突然想到,既然是队列的属性,那么把它设计成一个类,然后创建队列对象不就行了? 

之后又出现了一个疑惑,就是假如我把数据填满,rear此刻是最大值,然后从我头部拿掉几个,此时数据还能塞得进去吗?由于我塞数据的时候是以rear和最大值做的比较,所以会提示数组已满,但实际上我拿掉几个之后头部又空了,但是没办法从头部塞数据。要怎么办呢?

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MyQueen {

    private int[] content;
    private int maxSize;
    private int front = -1;
    private int rear = -1;

    private static final int DEFAULT_SIZE = 5;

    public MyQueen() {
        this.maxSize = DEFAULT_SIZE;
        content = new int[DEFAULT_SIZE];
    }

    // 如果传递了异常数据怎么处理??
    public MyQueen(int maxSize) {
        this.maxSize = maxSize;
        content = new int[maxSize];
    }

    public void addQueen(int value) {
        if (rear >= maxSize - 1) {
            System.out.println("队列已满,无法再添加数据,且本队列暂不提供扩容");
            return;
        }
        rear++;
        content[rear] = value;
    }

    public int getRearValue() {
        if (front == rear) {
            System.out.println("队列为空");
            return 0; // 队列为空的时候应该怎么处理返回值呢?
        }
        front++;
        return content[front];
    }

    @Override
    public String toString() {
        List<Integer> temp = new ArrayList<>(10);
        for (int i = content.length -1; i >= 0; i--) {
            temp.add(content[i]);
        }
        Object[] result = temp.toArray();
        return Arrays.toString(result);
    }
}

需要注意的是:rear指向的是尾部的数据,front指向的是头部数据的前一个位置。而且这里的取数据并不是说真的把数据取没了,而是模拟的队列,但实际中取过的头部数据还在数组中,所以在遍历显示的时候不能遍历整个数组,要知道队列的首尾序号是front + 1和rear。

我们对这一代码进行优化,优化点为:

当队列为空无法获取数据时,不能简单的返回一个数据,可以使用抛异常来进行处理。

public int getRearValue() {
    if (front == rear) {
        throw new RuntimeException("队列为空");
    }
    front++;
    return content[front];
}

如果仅仅是上面的那种实现,则会出现假溢出的问题,即就算头部的数据在逻辑上被取出来了,但是如果尾部此时是满的,则无法添加数据,需要优化,即环形队列

环形队列

对于环形队列,我们需要有这样的一种思路,就是把数组想象成一个环,这样当末尾满的时候,就会再顺延到开头。

这种情况下,数组的空间就可以循环利用,但同时也会牺牲掉一个数组空间来作为代价

既然是环形,那么在算法上就会涉及到一个取模%的问题,如何取模一会儿再说,先理解一下环形队列的实现思路

1、调整front指向第一个元素,即初始值为0;

2、调整rear指向最后一个元素的后一个位置,这就意味着,永远会有一个空间是没有数据的,单纯用来指示rear,初始值也为0;(这里要注意的是,front和rear的指向并没有定论,只要能够实现循环队列即可)

3、当队列满的时候(此时会有一个预留的空余空间),如何判断已经满了? 

对于上面的图,综合看来可以使用(rear + 1)% maxSize == front来判断已经满了。但我又产生了一个疑问,front和rear是在0到3的范围内变化还是可以无限递增?如果是无限递增的话,那么根据公式来看,front是余数,不可能会超过maxSize,也就是说可以确定front是在0—3范围内变化,那么rear呢?其实rear也是在0—3范围之间的。

但是有一点我们需要注意,front和rear在每一次操作中都会增加,但是我们需要通过一些简单的公式来将他们的值限定在 0—3之间,即增加后对maxSize取模。

4、如何判断队列为空?依然使用front == rear来判断。

5、如何获取队列中有效数据的个数?使用(maxSize + (rear - front))% maxSize来获取即可,数据肯定不会超过maxSize - 1。

综合代码如下:

public class CircleQueen {

    private int[] content;
    private int maxSize;
    private int front;
    private int rear;

    public CircleQueen(int maxSize) {
        front = 0;
        rear = 0;
        this.maxSize = maxSize;
        content = new int[maxSize];
    }

    // 判断队列是否为空
    private boolean isEmpty() {
        return front == rear; // 此处不要像下面一样写多余的代码
//        if (front == rear) {
//            return true;
//        } else {
//            return false;
//        }
//        return front == rear ? true : false;
    }

    // 判断队列是否满了
    private boolean isFull() {
        return (rear + 1) % maxSize == front; // 总结出的规律
    }

    // 添加数据。每次添加数据,下标都会增长,但是要通过取模限定在maxSize - 1的范围内
    public void addToQueen(int value) {
        if (isFull()) {
            System.out.println("队列已满");
            return;
        }
        content[rear] = value; // 先放值
        rear = (rear + 1) % maxSize; // 取模限定下标
    }

    // 获取数据
    public int getValue() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        int value = content[front];
        front = (front + 1) % maxSize;
        return value;
    }

    // 打印队列数据
    public void printQueen() {
        int length = (rear - front + maxSize) % maxSize; // rear可能比front要小
        for (int i = front; i < front + length; i++) { // 注意这里i的范围
            System.out.println(content[i % maxSize]);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值