Java 循环队列原理与用法详解

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java 循环队列原理与用法详解

  点击上方 **好好学java **,选择 **星标 **公众号


重磅资讯、干货,第一时间送达
今日推荐:iphone 也是办公神器,用了就知道了,不行送你一个试试个人原创+1博客:点击前往,查看更多

链接:https://segmentfault.com/a/1190000022046581

在正式进行循环队列学习之前,我们先来看看在顺序队列中删除队首元素出现的问题:

(1)设一个容量为capacity=8,size=5(a,b,c,d,e)的数组,左侧为队首、右侧为队尾。

(2)出队一个元素后,需整体往前移动一位

出队:

整体前移一位:

关于该种操作方式我们很容易得出时间复杂度为O(n)。

这时我们就想可不可以在出队元素后,整体元素不往前移,而是在数组中记下队首front是谁,同时队尾tail指向在下一次元素入队时的位置,这样当再有出队时只需要维护一下front的指向即可,而不需移动元素。就这样我们就有了循环队列的情况。

1.循环队列原理

(1)初始,数组整体为空时,队首front、队尾tail指向同一个位置(数组索引为0的地方)也即front==tail 时队列为空

(2)当往数组中添加元素后

(3)出队一个元素,front指向新的位置

(4)入队元素,tail叠加

(5)当tail不能再增加时,数组前面还有空余,此时循环队列就该出场了。

此时数组应该变为这样:

在往数组中添加一个元素:

这样数组就已经满了(tail+1==front 队列满),开始出发扩容操作。capacity中,浪费一个空间。

为了tail能返回到数组的前面位置,将队列满的表达式变为 (tail+1)%c==front这样数组就可以循环移动了。

2.循环队列代码实现

新建一个类LoopQueue并实现接口Queue。

2.1、接口Queue代码如下:


package Queue;

public interface Queue<E> {
    //获取队列中元素个数
    int getSize();

    //队列中元素是否为空
    boolean isEmpty();

    //入队列
    void enqueue(E e);

    //出队列
    E dequeue();

    //获取队首元素
    E getFront();
}

2.2、LoopQueue相关代码:


package Queue;

//循环队列
public class LoopQueue<E> implements Queue<E> {
    private E[] data;
    private int front, tail;
    private int size;//队列中元素个数

    //构造函数,传入队列的容量capacity构造函数
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity + 1];//浪费与一个空间
        front = 0;
        tail = 0;
        size = 0;
    }

//无参构造函数,默认队列的容量capacity=10
public LoopQueue() {
    this(10);
}

//真正容量
public int getCapacity() {
    return data.length - 1;
}

//队列是否为空
@Override
public boolean isEmpty() {
    return front == tail;
}

//队列中元素个数
@Override
public int getSize() {
    return size;
}

//入队列操作
@Override
public void enqueue(E e) {
    if ((tail + 1) % data.length == front) {//队列已满,需要扩容
        resize(getCapacity() * 2);
    }
    data[tail] = e;
    tail = (tail + 1) % data.length;
    size++;
}

//出队操作

@Override
public E dequeue() {
    if (isEmpty()) {
    throw new IllegalArgumentException("队列为空");
    }

    E ret = data[front];
    data[front] = null;//手动释放
    front = (front + 1) % data.length;
    size--;
    if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
        resize(getCapacity() / 2);
    }
    return ret;
}

//获取队首元素
@Override
public E getFront() {
    if (isEmpty()) {
      throw new IllegalArgumentException("队列为空");
    }
    return data[front];
}

//改变容量
private void resize(int newCapacity) {
    E[] newData = (E[]) new Object[newCapacity + 1];
    for (int i = 0; i < size; i++) {
        newData[i] = data[(front + i) % data.length];//循环数组防止越界
    }
    data = newData;
    front = 0;
    tail = size;
}

@Override
public String toString() {
    StringBuilder res = new StringBuilder();
    res.append(String.format("Queue:size=%d, capacity=%d\n", size, getCapacity()));
    res.append("front [");
    for (int i = front; i != tail; i = (i + 1) % data.length) {
        res.append(data[i]);
        if ((i + 1) % data.length != tail) {
        res.append(",");
        }
    }
    res.append("] tail");
    return res.toString();
    }

}

在关于LoopQueue类中需要注意的:

(1)第11行中的+1是capacity需要浪费一个空间,故在实例化是多加1


data = (E[]) new Object[capacity + 1];//浪费与一个空间

(2)地24行真正的容量是data.length-1,这是由于有一个空间是浪费的。


data.length - 1;

(3)关于入队中第46行tail值的说明

为了保证入队是循环操作,tail值的变化规律为


tail = (tail + 1) % data.length;

(4)关于82行的数据迁移操作,取余操作是为了防止循环数组时越界。


newData[i] = data[(front + i) % data.length];//循环数组防止越界

2.3、直接在LoopQueue中添加一个main函数进行测试,相关代码如下:


//测试用例
public static void main(String[] args) {
    LoopQueue<Integer> queue = new LoopQueue<Integer>();
    for (int i = 0; i < 10; i++) {
        queue.enqueue(i);
        System.out.println(queue);

        if(i%3==2){//每添加3个元素出队列一个
        queue.dequeue();
        System.out.println(queue);
    }

  }

}

结果为:

3.循环队列时间复杂度

到此我们就实现了一个循环队列操作,解决了在顺序队列中出队时的时间复杂度为O(n)的情况,在循环队列中出队的时间复杂度为O(1)。

原文地址:https://sihai.blog.csdn.net/article/details/109465252

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java 循环队列原理与用法详解


 上一篇
一文深入了解 Redis 内存模型,Redis 的快是有原因的! 一文深入了解 Redis 内存模型,Redis 的快是有原因的!
  点击上方 **好好学java **,选择 **星标 **公众号 重磅资讯、干货,第一时间送达 今日推荐:八个开源的 Spring Boot 学习资源,你值得拥有个人原创+1博客:点击前往,查看更多 作者:编程迷思 链接:https
2021-04-04
下一篇 
如何异地加载 Spring Boot 配置文件? 如何异地加载 Spring Boot 配置文件?
  点击上方 **好好学java **,选择 **星标 **公众号 重磅资讯、干货,第一时间送达 今日推荐:iphone 也是办公神器,用了就知道了,不行送你一个试试个人原创+1博客:点击前往,查看更多 链接:https://segm
2021-04-04