• 2.5循环队列


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

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

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

    #出队

      

    #2整体前移一位

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

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

    2.循环队列原理

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

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

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

    (4)入队元素,tail叠加

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

     

    此时数组应该变为这样:

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

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

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

    3.循环队列代码实现

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

    #1:接口Queue代码如下:

    package Queue;
    
    public interface Queue<E> {
        //获取队列中元素个数
        int getSize();
    
        //队列中元素是否为空
        boolean isEmpty();
    
        //入队列
        void enqueue(E e);
    
        //出队列
        E dequeue();
    
        //获取队首元素
        E getFront();
    }

    #2:LoopQueue相关代码:

      1 package Queue;
      2 
      3 //循环队列
      4 public class LoopQueue<E> implements Queue<E> {
      5     private E[] data;
      6     private int front, tail;
      7     private int size;//队列中元素个数
      8 
      9     //构造函数,传入队列的容量capacity构造函数
     10     public LoopQueue(int capacity) {
     11         data = (E[]) new Object[capacity + 1];//浪费与一个空间
     12         front = 0;
     13         tail = 0;
     14         size = 0;
     15     }
     16 
     17     //无参构造函数,默认队列的容量capacity=10
     18     public LoopQueue() {
     19         this(10);
     20     }
     21 
     22     //真正容量
     23     public int getCapacity() {
     24         return data.length - 1;
     25     }
     26 
     27     //队列是否为空
     28     @Override
     29     public boolean isEmpty() {
     30         return front == tail;
     31     }
     32 
     33     //队列中元素个数
     34     @Override
     35     public int getSize() {
     36         return size;
     37     }
     38 
     39     //入队列操作
     40     @Override
     41     public void enqueue(E e) {
     42         if ((tail + 1) % data.length == front) {//队列已满,需要扩容
     43             resize(getCapacity() * 2);
     44         }
     45         data[tail] = e;
     46         tail = (tail + 1) % data.length;
     47         size++;
     48     }
     49 
     50     //出队操作
     51 
     53     @Override
     54     public E dequeue() {
     55         if (isEmpty()) {
     56             throw new IllegalArgumentException("队列为空");
     57         }
     58 
     59         E ret = data[front];
     60         data[front] = null;//手动释放
     61         front = (front + 1) % data.length;
     62         size--;
     63         if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
     64             resize(getCapacity() / 2);
     65         }
     66         return ret;
     67     }
     68 
     69     //获取队首元素
     70     @Override
     71     public E getFront() {
     72         if (isEmpty()) {
     73             throw new IllegalArgumentException("队列为空");
     74         }
     75         return data[front];
     76     }
     77 
     78     //改变容量
     79     private void resize(int newCapacity) {
     80         E[] newData = (E[]) new Object[newCapacity + 1];
     81         for (int i = 0; i < size; i++) {
     82             newData[i] = data[(front + i) % data.length];//循环数组防止越界
     83         }
     84         data = newData;
     85         front = 0;
     86         tail = size;
     87     }
     88 
     89 
     90     @Override
     91     public String toString() {
     92         StringBuilder res = new StringBuilder();
     93         res.append(String.format("Queue:size=%d, capacity=%d
    ", size, getCapacity()));
     94         res.append("front [");
     95         for (int i = front; i != tail; i = (i + 1) % data.length) {
     96             res.append(data[i]);
     97             if ((i + 1) % data.length != tail) {
     98                 res.append(",");
     99             }
    100         }
    101         res.append("] tail");
    102         return res.toString();
    103     }
    104 
    105 
    10121 
    122 }

    在关于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];//循环数组防止越界

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

    结果为:

     

    4.循环队列时间复杂度

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

    源码地址 https://github.com/FelixBin/dataStructure/blob/master/src/Queue/LoopQueue.java

  • 相关阅读:
    专题一 Java基础语法
    IOC属于那种设计模式?
    java桌面应用开发
    Gitblit
    基于JSoup库的java爬虫开发学习——小步快跑
    tomcat和solr的整合——小步快跑
    SSM mapper文件SQL语句里面的 小于号 Tag name expected 无法识别解读
    Java从FTP服务器中获取PDF文件数据
    idea 控制台log日志中文乱码解决方案
    Java开发文档Swagger的使用详细教程
  • 原文地址:https://www.cnblogs.com/wfaceboss/p/10628345.html
Copyright © 2020-2023  润新知