大话数据结构-Java代码实现-队列

文章介绍了队列的基本概念,包括其FIFO特性,队头和队尾的操作,并详细阐述了循环队列和链式存储结构在解决顺序存储溢出问题上的应用。同时,讨论了阻塞队列和非阻塞队列的区别以及有界和无界队列的特性。此外,还提及了Quene和Deque接口在Java中的地位和功能差异。

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

队列


定义

  • 队列(Quene)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

  • 队列是一种先进先出的线性表,简称FIFO(First In First Out)

  • 允许插入的一端叫队尾,允许删除的一端叫队头

myQuene接口

  • 代码实现

public interface myQuene {

    /**
     * 判断队列是否为空
     * @return
     */
    public boolean isEmpty();

    /**
     * 判断队列是否满
     * @return
     */
    public boolean isFull();

    /**
     * 将元素加入队列
     * @param obj 要加入的元素
     * @throws Exception
     */
    public void add(Object obj) throws Exception;

    /**
     * 将队尾元素移出队列
     * @return 队尾元素
     * @throws Exception
     */
    public Object remove() throws Exception;

    /**
     * 看一眼队尾元素
     * @return 队尾元素
     * @throws Exception
     */
    public Object peek() throws Exception;

    /**
     *
     * @return 队列长度
     */
    public int getLength();

    /**
     * 遍历队列
     */
    public void list();
}

循环队列LoopQuene

  • 对于顺序存储队列:

  • 在队尾追加元素,不需要移动任何元素,时间复杂度为O(1)

  • 但在队头删除元素,需要把队列中的所有元素向前移动,保证队头(下标为0的位置)不为空。时间复杂度为O(n)

  • 我们使用两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置

  • 如果使用顺序存储,空间无法重复利用,产生"加溢出"

  • 使用循环队列

  • 队列头尾相接的顺序存储结构

  • 满了之后,rear指向下标为0的位置,产生无法判断队列满还是空的问题

  • 解决思路:

  • 队列满时,修改条件,保留一个元素空间,即数组中还有一个空闲单元

  • 队列满时,可能rear和front差一个位置,可能差一圈

  • 分析得到队列满的条件(rear + 1) % QueneSize == front

  • 计算队列长度的公式(rear - front + QueneSize) % QueneSize

  • 添加元素后,rear指针的新位置为rear = (rear + 1) % QueneSize

  • 删除元素后,front指针的新位置为front = (front + 1) % QueneSize

  • 代码实现:

public class loopQuene implements myQuene{

    //定义四个属性
    private int front; //头指针
    private int rear; //尾指针
    private int maxSize; //定义队列的最大长度
    private Object[] arr; //队列的数据存放在数组arr中

    public loopQuene(int maxSize){
        this.maxSize = maxSize;
        this.arr = new Object[maxSize];
        front = 0; //头指针和尾指针都初始化为0
        rear = 0;
    }

    @Override
    public boolean isEmpty() {
        return front == rear; //如果头尾指针重合,队列为空
    }

    @Override
    public boolean isFull() {
        return (rear + 1) % maxSize == front; //判断队列满的条件
    }

    @Override
    public void add(Object obj) throws Exception {
        if (isFull()){
            throw new Exception("队列已满!");
        }
        arr[rear] = obj;
        rear = (rear + 1) % maxSize; //注意是循环队列,加入元素位置后,rear的计算方法
    }

    @Override
    public Object remove() throws Exception {
        if (isEmpty()){
            throw new Exception("队列空!");
        }

        Object res = arr[front];
        front = (front + 1) % maxSize; //注意删除元素后front的位置
        return res;
    }

    @Override
    public Object peek() throws Exception {
        if (isEmpty()){
            throw new Exception("队列空!");
        }
        return arr[front];
    }

    @Override
    public int getLength() {
        return (rear - front + maxSize) % maxSize; //队列长度的计算公式
    }

    @Override
    public void list() {
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

    }
}
  • 循环队列依然可能面临数组溢出的问题,可以使用链式存储结构

链式存储结构LinkedQuene

  • 链队列因为数据流向都是单向的,因此使用单链表就可以实现。

  • 又根据队列的“先进先出”规则我们可以知道我们需要同时关注首结点和尾结点,因此需要声明尾结点,这样我们在尾部插入时就会很方便,若是不使用尾结点的话,每次队列的插入操作就需要遍历到链表的尾部进行插入,这样每次插入的时间复杂度都变成了O(n),这是很耗费性能的。

  • 因此我们需要提供带有尾结点的单链表,同时提供首结点,用以标注链表的开始,注意这里是首结点不是头结点。

  • 首结点与头结点的区别是首结点存储数据,头结点是不存储数据的。

  • 注意一个细节

  • 链队列最开始头节点和尾结点重合,这个结点不存放数据;

  • 当第一个数据进来时,需要单独讨论,这是头尾结点不移动,存放数据即可

  • 之后添加结点,头节点移动

  • 删除时同样需要讨论

public class linkedQuene {

    Node front;
    Node rear;

    //初始化头尾结点重合,创建新节点
    public linkedQuene() {
        front = rear = new Node();
    }

    //注意,如果只插入一个数据,仍然是front == rear
    public boolean isEmpty() {
        return (front == rear && front.data == null);
    }

    public void add(Object obj) throws Exception {

        Node node = new Node(null, obj);
        if (rear.data == null) {
            front = node;
            rear = node; //根据是否是第一个结点分类讨论
        } else {
            rear.next = node;
            rear = rear.next;
        }
    }

    public Object remove() throws Exception {
        if (isEmpty()) {
            throw new Exception("队列空!");
        }
        if (rear == front) { //根据是否是第一个结点分类讨论
            Object res = rear.data;
            rear.data = null;
        }
        Object res = front.data;
        front = front.next;
        return res;
    }

    public Object peek() throws Exception {
        return rear.data;
    }

    public int getLength() {
        Node temp = front;
        int len = 0;
        while (temp != null) {
            temp = temp.next;
            len++;
        }
        return len;
    }

    public void list() {
        Node temp = front;
        for (int i = 0; i < this.getLength(); i++) {
            System.out.println(temp.toString());
            temp = temp.next;
        }
    }
}
  • 阻塞和非阻塞

  • 阻塞队列

  • 入列(添加元素)时,如果元素数量超过队列总数,会进行等待(阻塞),待队列的中的元素出列后,元素数量未超过队列总数时,就会解除阻塞状态,进而可以继续入列;

  • 出列(删除元素)时,如果队列为空的情况下,也会进行等待(阻塞),待队列有值的时候即会解除阻塞状态,进而继续出列;

  • 阻塞队列的好处是可以防止队列容器溢出;只要满了就会进行阻塞等待;也就不存在溢出的情况;

  • 只要是阻塞队列,都是线程安全的;

  • 非阻塞队列

  • 不管出列还是入列,都不会进行阻塞,

  • 入列时,如果元素数量超过队列总数,则会抛出异常,

  • 出列时,如果队列为空,则取出空值;

  • 一般情况下,非阻塞式队列使用的比较少,一般都用阻塞式的对象比较多;

  • 有界和无界

  • 有界:有界限,大小长度受限制

  • 无界:无限大小,其实说是无限大小,其实是有界限的,只不过超过界限时就会进行扩容,就和ArrayList 一样,在内部动态扩容

  • Quene和Deque

  • Quene以及Deque都是继承于Collection,Deque是Quene的子接口。

  • Quene是先进先出的单向队列,Deque是双向队列。

  • 也就是说Deque是Queue的子类,可以把它当作队列来使用。

  • Deque支持两端元素插入和移除的线性集合。也就是Deque可以在两边都操作元素:新增、删除、访问元素等。而Quene 只能在队首对元素进行操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值