• 数据结构 -- 队列Queue


    一、队列简介

    定义

    队列(queue)在计算机科学中,是一种先进先出的线性表。 它只允许在表的前端进行删除操作,而在表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

    1. 队列是一种线性结构;
    2. 相比数组,队列对应操作的是数组的子集;
    3. 只能从一端(队尾)添加元素,只能从另一端(队首)取出元素 。先进先出的数据结构(先到先得First In First Out【FIFO】)。

     二、代码实现

    1. 队列接口

    public interface Queue{
        int getSize(); //返回元素的个数
        E getFront(); //返回队首元素内容
        boolean isEmpty(); //判断是否为空
        void enqueue(E e); // 入队
        E dequeue(); //出队
    }

     2、循环队列

    循环队列中有两个新词,两个指针

    • front 指向队列的第一个元素,初始指向0
    • tail 指向队列的最后一个元素的后一个位置,初始指向0
    • 循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。 [1]  循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。
    public class LoopQueue<E> implements Queue<E> {
    
        private E[] data;
        //指向队列的第一个元素,初始指向0
        private int front;
        //指向队列的最后一个元素的后一个位置,初始指向0
        private int tail;
        //元素数量
        private int size;
    
        public LoopQueue(int capacity){
            data = (E[]) new Object[capacity + 1];
            front = 0;
            tail = 0;
            size = 0;
        }
        public LoopQueue(){
            this(10);
        }
    
        @Override
        public int getSize() {
            return size;
        }
        /**
         * 因为容量放的时候多了个1,所以get容量的时候,需要减1
         * @return
         */
        public int getCapacity(){
            return data.length - 1;
        }
        /**
         * 当front和tail的值相等时,队列为空,初始两个指向的是同一个值(只有初始的时候,指向的是同一个地方)
         * @return
         */
        @Override
        public boolean isEmpty() {
            return front == tail;
        }
        /**
         * 1.if((tail + 1) % data.length == front) 如果tail + 1 超过了data.length的大小,
         *   代表当前tail指向已经超出了容量的大小,因为是循环式,所以需要tail去循环头元素中查看值是否有被占用,
         *   如果 == front 代表循环头没有,就需要扩容了。
         * 2.举例: 元素容量为8,tail目前指向7 front 指向2
         *         if((7 + 1) % 8 == 2 )  if(0 == 2) 这里是false,因为front指向了2,所以代表 第0,1位是没有值的
         *         所以这个值需要在在第0位放(空间利用)
         * 3.data[tail] = param  tail当前指向的地方需要赋值,然后tail自增 循环体 的1,size+1
         * @param param
         */
        @Override
        public void enqueue( E param) {
            if ((tail + 1) % data.length == front) {
                resize(getCapacity() * 2);
            }
            data[tail] = param;
            tail = (tail + 1) % data.length;
            size++;
        }
        /**
         * 1.如果队列为空抛出异常
         * 2.用ret变量来接受当前队列头的值
         * 3.接收成功之后将,队列头元素置空
         * 4.front指针指向下一个元素
         * 5.size大小-1
         * 6.如果size大小占据了容量的1/4和size为容量的1/2且不等于0的时候,对容量进行缩减,缩减为原来容量的1/2
         * 7.返回ret变量
         * @return
         */
        @Override
        public E dequeue() {
            if(isEmpty()){
                throw new IllegalArgumentException("Cannot dequeue from an empty queue");
            }
            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("Queue is empty");
            return data[front];
        }
        /**
         * 扩充队列的容量
         * 1.front代表了当前元素初始位置的指向
         * 2.newData的第i位元素,应该等于 i + front % data.length 的值
         * 3.举例:元素容量20,i 等于 0 ,front 等于 2,结果: newData[0] = data[(0 + 2) %  20]
         *         = data[2]   意思就是,newData的第一位元素,应该等于data有值的第一位元素
         *         % data.length 的原因主要是为了防止数组越界错误
         * 4.新数组赋值完成需要将 front 重新指向0,因为新数组的front指针是从0开始的。
         *   tail最后要指向等于size大小的值,
         * @param newCapacity
         */
        private void resize(int newCapacity){
            E[] newData = (E[]) new Object[newCapacity + 1];
            for (int i=0; i < size; i++){
                newData[i] = data[(i + front) % data.length];
            }
            data = newData;
            front = 0;
            tail = size;
        }
        /**
         * 1.元素从 front位置开始循环遍历,i的值不能等于tail,
         *   也就是到tail的前一位,i = i + 1 且%data.length,
         *   因为i有可能从循环头重新开始
         * 2.( i + 1 ) % data.length != tail  如果当前i + 1 % data.length
         *   不等于tail表示不到最后一个元素,就拼接,
         * @return
         */
        @Override
        public String toString(){
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(String.format("LoopQueue:size = %d, capacity = %d
    ",size, getCapacity()));
            stringBuilder.append("front [");
            for (int i=front; i != tail; i = (i + 1)%data.length){
                stringBuilder.append(data[i]);
                if ((i + 1)%data.length != tail){
                    stringBuilder.append(",");
                }
            }
            stringBuilder.append("] tail");
            return stringBuilder.toString();
        }
    }

    循环队列测试类

    public class LoopQueueTest {
        public static void main(String[] args) {
            LoopQueue<Integer> integerArrayQueue = new LoopQueue<>();
            for (int i = 0; i < 10; i++) {
                integerArrayQueue.enqueue(i);
                System.out.println(integerArrayQueue);
    
                if(i % 3 == 2){
                    integerArrayQueue.dequeue();
                    System.out.println(integerArrayQueue);
                }
            }
        }
    }
    //测试结果
    LoopQueue:size = 1, capacity = 5
    front [0] tail
    LoopQueue:size = 2, capacity = 5
    front [0,1] tail
    LoopQueue:size = 3, capacity = 5
    front [0,1,2] tail
    LoopQueue:size = 2, capacity = 5
    front [1,2] tail
    LoopQueue:size = 3, capacity = 5
    front [1,2,3] tail
    LoopQueue:size = 4, capacity = 5
    front [1,2,3,4] tail
    LoopQueue:size = 5, capacity = 5
    front [1,2,3,4,5] tail
    LoopQueue:size = 4, capacity = 5
    front [2,3,4,5] tail
    LoopQueue:size = 5, capacity = 5
    front [2,3,4,5,6] tail
    LoopQueue:size = 6, capacity = 10
    front [2,3,4,5,6,7] tail
    LoopQueue:size = 7, capacity = 10
    front [2,3,4,5,6,7,8] tail
    LoopQueue:size = 6, capacity = 10
    front [3,4,5,6,7,8] tail
    LoopQueue:size = 7, capacity = 10
    front [3,4,5,6,7,8,9] tail

    测试结果是正确的,符合队列结构的数据存取,但基于自定义数组来实现,所以会调用数组方法的removeFirst方法,删除第一个元素的同时,会重新将后面所有元素前移,索引前移,均摊时间复杂度为O(n)。 

    3. 数组实现队列

    public class ArrayQueue<E> implements Queue<E>{
    
        Array<E> array; //详情内容:https://www.cnblogs.com/FondWang/p/11806545.html
    
        //初始化大小
        public ArrayQueue(int capacity){
            array=new Array<E>(capacity);
        }
    
      //无参构造器
        public ArrayQueue(){
            array=new Array<E>();
        }
    
       //入队。只能从队尾添加数据
        @Override
        public void enqueue(E param) {
            array.addLast(param);
        }
        //出队。只能从队首添加内容
        @Override
        public E dequeue() {
            return array.removeFirst();
        }
        //返回队首的元素
        @Override
        public E getFront() {
            return array.getFirst();
        }
       
        @Override
        public int getSize() {
            return array.getSize();
        }
    
        @Override
        public boolean isEmpty() {
            return array.isEmpty();
        }
    
        @Override
        public String toString(){
            StringBuffer sb = new StringBuffer();
            sb.append("front: ");
            sb.append("[");
            for(int i=0;i<array.getSize();i++){
                sb.append(array.get(i));
                if(i!=array.getSize()-1){
                    sb.append(", ");
                }
            }
            sb.append("]  tail");
            return sb.toString();
        }
    }

     数组队列测试类

    public class ArrayQueueTest {
        public static void main(String[] args) {
            ArrayQueue<Integer> integerArrayQueue = new ArrayQueue<>();
            for (int i = 0; i < 10; i++) {
                integerArrayQueue.enqueue(i);
                System.out.println(integerArrayQueue);
    
                if(i % 3 == 2){
                    integerArrayQueue.dequeue();
                    System.out.println(integerArrayQueue);
                }
            }
        }
    }
    //测试结果
    ArrayQueue:front [0] tail
    ArrayQueue:front [0, 1] tail
    ArrayQueue:front [0, 1, 2] tail
    ArrayQueue:front [1, 2] tail
    ArrayQueue:front [1, 2, 3] tail
    ArrayQueue:front [1, 2, 3, 4] tail
    ArrayQueue:front [1, 2, 3, 4, 5] tail
    ArrayQueue:front [2, 3, 4, 5] tail
    ArrayQueue:front [2, 3, 4, 5, 6] tail
    ArrayQueue:front [2, 3, 4, 5, 6, 7] tail
    ArrayQueue:front [2, 3, 4, 5, 6, 7, 8] tail
    ArrayQueue:front [3, 4, 5, 6, 7, 8] tail
    ArrayQueue:front [3, 4, 5, 6, 7, 8, 9] tail
    
    

     因为引用了指针这个概念,删除的时候索引不会重排,均摊时间复杂度为O(1)

    4. 循环队列和数组队列 效率对比

    测试代码

    import java.util.Random;
    public class Main {
        private static double testQueue(Queue<Integer> q, int opCount){
            long startTime = System.nanoTime();
            Random random = new Random();
            for (int i=0;i<opCount; i++){
                q.enqueue(random.nextInt(Integer.MAX_VALUE));
            }
            for (int i=0; i<opCount;i++){
                q.dequeue();
            }
            long endTime = System.nanoTime();
            return (endTime - startTime) / 1000000000.0;
        }
    
        public static void main(String[] args) {
            int opCount = 100000;//十万数据增删效率
    
            ArrayQueue arrayQueue = new ArrayQueue();
            double time1 = testQueue(arrayQueue,opCount);
            System.out.println("ArrayQueue, time:" + time1 + "s");
    
            LoopQueue loopQueue = new LoopQueue();
            double time2 = testQueue(loopQueue,opCount);
            System.out.println("LoopQueue, time:" + time2 + "s");
    
            System.out.println("loopQueue队列数ArrayQueue的 " + Math.round(time1/time2) + "倍");
        }
    }

    测试结果

    ArrayQueue, time:3.78317767s
    LoopQueue, time:0.011734084s
    loopQueue队列 是ArrayQueue 的 322

    //测试三次: 322、327、322,平均(322+327+323)/ 3 约为 323倍

     

  • 相关阅读:
    c#RSA的SHA1加密与AES加密、解密
    c#后台代码请求访问api接口
    Hbuilder给手机发送短信与拨打电话
    Hbuilder获取手机当前地理位置的天气
    plus.webview.create( url, id, styles, extras )参数及说明
    九九乘法表+冒泡排序(校园回忆录)
    c#数据处理总结(分组、交并差与递归)
    Hbuilder MUI 下拉选择与时间选择器
    JAVA常用开源工具与项目
    mysql 中常用功能
  • 原文地址:https://www.cnblogs.com/FondWang/p/11808221.html
Copyright © 2020-2023  润新知