用Java简单模拟实现循环队列 以及 Queue接口的介绍

498 阅读4分钟

1. 用 Java 模拟队列

1.1 什么是队列?

  队列(Queue)是一种基于先进先出原则的线性数据结构,数据项在队列末尾添加,从队列头部删除。

队列.gif

1.2 模拟循环队列

用java简单模拟单链表和双向链表 and LinkedList类的介绍 - 掘金 (juejin.cn)

  我们知道 队列 是队尾进、队头出,那么我们用什么来储存队列的数据呢?Java中的 LinkedList(双向链表)实现了Queue接口,也就是说Java中的队列的底层是用双向链表实现的,入队就是尾插、出队就是头删,效率是很高的。

  还有一种方式,就是用数组来模拟队列,用两个指针(变量)来维护队头与队尾,入队尾指针后移一位,出队头指针后移一位。但是这会遇到一个问题,由于数组是静态的,尾指针总有移到最后一个位置的时候,这时如果扩容,前面的空间就会被浪费掉(头指针会后移)。这时就用循环队列来储存数据,就如下图。

队列2.gif

  (1)在开始模拟实现前,先约定当front == trail时队列为空,当(trail + 1)% length == front时队列满

image.png
public class MyQueue {
    private int[] data;
    public int front;
    public int trail;
    //构造 k 大小的队列
    public MyQueue(int k){
        this.data = new int[k+1];//为什么是 k+1 ?因为要装k个数据,多的一个用来判断队列满。
    }
    
    //判断空
    public boolean isEmpty(){
        return this.front == this.trail;
    }
    
    //判断满
    public boolean isFull(){
        return (trail + 1) % data.length == front;
    }
    
    
}

  (2)入队:这里没有进行扩容,大家可以自己写一个扩容的版本。

//入队
public boolean offer(int val){
    if(isFull()){
        return false;
    } else {
        data[trail] = val;
        trail = (trail + 1) % data.length;//为什么这么写?想一想 trail 为数组的最后一个时。
    }
    return true;
}

  (3)出队:

//出队
public boolean poll(){
    if(isEmpty()){
        return false;
    }else {
        front = (front + 1) % data.length;
        return true;
    }
}

  (4)查看队头元素:

//查看队头
public int peek() {
    if (isEmpty()) {
        return -1;
    } else {
        return this.data[front];
    }
}

  (4)计算队列中数据的个数:

//计算队列中数据的个数
public int size(){
    return (trail - front + data.length) % data.length;
}

假设capacity为数组的长度。

  1. 首先,计算队列中元素的数量,即tail - head
  2. 如果tail - head为正,说明尾部指针在头部指针之后,即没有绕回数组的开头位置,此时直接返回tail - head即可。
  3. 如果tail - head为负,说明尾部指针在头部指针之前,即绕回了数组的开头位置。这种情况下,我们需要将tail - head加上数组的长度capacity,才能得到队列中元素的数量。
  4. 最后,使用% capacity将结果取模,为什么要取模?因为出现第 2 种情况的时候多加了一个capacity

  (本篇只实现了常用的方法)

1.3 汇总

public class MyQueue {
    private int[] data;
    public int front;
    public int trail;
    //构造 k 大小的队列
    public MyQueue(int k){
        this.data = new int[k+1];//为什么是 k+1 ?因为要装k个数据,多的一个用来判断队列满。
    }

    //判断空
    public boolean isEmpty(){
        return this.front == this.trail;
    }

    //判断满
    public boolean isFull(){
        return (trail + 1) % data.length == front;
    }

    //入队
    public boolean offer(int val){
        if(isFull()){
            return false;
        } else {
            data[trail] = val;
            trail = (trail + 1) % data.length;
        }
        return true;
    }

    //出队
    public boolean poll(){
        if(isEmpty()){
            return false;
        }else {
            front = (front + 1) % data.length;
            return true;
        }
    }

    //查看队头
    public int peek() {
        if (isEmpty()) {
            return -1;
        } else {
            return this.data[front];
        }
    }
    
    //计算队列中数据的个数
    public int size(){
        return (trail - front + data.length) % data.length;
    }
}

2. Queue接口

2.1 Queue 接口的介绍

  Queue是一个接口,有多个实现类,包括LinkedListPriorityQueue(后面更新)。LinkedList实现了Dueue接口,而Dueue继承了Queue接口,所以它可以用作普通的队列,也可以用作双向队列Deque

image.png

未命名绘图-第 2 页.drawio.png

  Deque 是双向队列,元素可以从队头出队和入队,也可以从队尾出队和入队,本篇就不做介绍了,大家可以自己试一试它的方法。

2.2 Queue接口提供的常用方法

常用方法描述
boolean add(E e)添加元素到队列末尾,如果队列已满,则抛出异常
boolean offer(E e)添加元素到队列末尾,如果队列已满,则返回false
E remove()移除并返回队列头部的元素,如果队列为空,则抛出异常
E poll()移除并返回队列头部的元素,如果队列为空,则返回null
E element()返回队列头部的元素,如果队列为空,则抛出异常
E peek()返回队列头部的元素,如果队列为空,则返回null
boolean isEmpty()检查队列是否为空
int size()返回队列中的元素数量
void clear()清空队列中的所有元素

  需要注意的是,Queue接口继承了Collection接口,因此它也包含了许多与Collection接口相同的方法。

public static void main(String[] args) {

    Queue<String> queue = new LinkedList<>();
    // 添加元素到队列末尾
    queue.add("apple");
    queue.add("banana");
    queue.add("cherry");

    // 返回队列头部的元素,但不移除它
    String first = queue.peek();
    System.out.println("队列头部的元素是:" + first);

    // 移除并返回队列头部的元素
    String item = queue.poll();
    System.out.println("移除的元素是:" + item);

    // 返回队列中的元素数量
    int size = queue.size();
    System.out.println("队列中的元素数量是:" + size);

    // 清空队列中的所有元素
    queue.clear();
}

结果:
队列头部的元素是:apple
移除的元素是:apple
队列中的元素数量是:2