第二十四篇 玩转数据结构——队列(Queue)

Posted xuezou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第二十四篇 玩转数据结构——队列(Queue)相关的知识,希望对你有一定的参考价值。

 
 
 
1.. 队列基础
  • 队列也是一种线性结构;
  • 相比数组,队列所对应的操作数是队列的子集;
  • 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素;
  • 队列的形象化描述如下图:
  • 技术分享图片

     

  • 队列是一种先进先出(First In First Out)的数据结构;
2.. 队列的实现
  • 任务目标如下:
  • Queue<E>
    ·void enqueue(E)   //入队
    ·E dequeue()   //出队
    ·E getFront()  //查看队首元素
    ·int getSize()   //查看队列中元素的个数
    ·boolean isEmpty()   //查看队列是否为空

     

  • 需要提一下,从用户的角度来看,只要实现上述操作就好,具体底层实现,用户并不关心,实际上,底层确实有多种实现方式。
  • 我们准备在之前实现的动态数组基础上,来实现"队列"这种数据结构。 
  • 先定义一个接口Interface,如下:
  • public interface Queue<E> {
        int getSize();
    
        boolean isEmpty();
    
        void enqueue(E e);
    
        E dequeue();
    
        E getFront();
    
    }

     

  • 实现基于Array类的ArrayQueue类,并进行测试:
  • public class ArrayQueue<E> implements Queue<E> {
        private Array<E> array;
    
        //构造函数
        public ArrayQueue(int capacity) {
            array = new Array<>(capacity);
        }
    
        //无参数构造函数
        public ArrayQueue() {
            array = new Array<>();
        }
    
        //实现getSize()方法
        @Override
        public int getSize() {
            return array.getSize();
        }
    
        //实现isEmpty方法
        @Override
        public boolean isEmpty() {
            return array.isEmpty();
        }
    
        //实现getCapacity方法
        public int getCapacity() {
            return array.getCapacity();
        }
    
        //实现enqueue方法
        @Override
        public void enqueue(E e) {
            array.addLast(e);
        }
    
        //实现dequeue方法
        @Override
        public E dequeue() {
            return array.removeFirst();
        }
    
        //实现getFront方法
        @Override
        public E getFront() {
            return array.getFirst();
        }
    
        //方便打印测试
        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
            res.append("Queue: ");
            res.append("front [");
            for (int i = 0; i < array.getSize(); i++) {
                res.append(array.get(i));
                if (i != array.getSize() - 1) {
                    res.append(", ");
                }
            }
            res.append("] tail");
            return res.toString();
        }
    
      // 测试
        public static void main(String[] args){
            ArrayQueue<Integer> queue = new ArrayQueue<>();
    
            // 测试入队
            for(int i=0;i<5;i++){
                queue.enqueue(i);
            }
            System.out.println(queue);
    
            // 测试出队
            queue.dequeue();
            System.out.println(queue);
        }
    }

     

  • 输出结果:
  • Queue: front [0, 1, 2, 3, 4] tail
    Queue: front [1, 2, 3, 4] tail

     

3.. 数组队列的时间复杂度分析:

  • ArrayQueue<E>
    ·void enqueue(E)   O(1)    均摊
    ·E dequeue()   O(n)
    ·E getFront()  O(1)
    ·int getSize()   O(1)
    ·boolean isEmpty()   O(1)

     

4.. 循环队列
  • 数组队列的出队操作的复杂度是O(n),性能很差,解决方法就是使用循环队列(Loop Queue)
  • 循环队列的示意图如下:
  • 技术分享图片
  • 技术分享图片

     

  • 实现循环队列的业务逻辑,并进行测试:
  • public class LoopQueue<E> implements Queue<E> {
    
        private E[] data;
        private int front, tail;
        private int size;
    
        //构造函数
        public LoopQueue(int capacity) {
            data = (E[]) new Object[capacity + 1];
            front = 0;
            tail = 0;
            size = 0;
        }
    
        //无参数构造函数
        public LoopQueue() {
            this(10); //直接调用有参数的构造函数,然后传入一个默认值
        }
    
        //实现getCapacity方法
        public int getCapacity() {
            return data.length - 1;
        }
    
        //实现isEmpty方法
        @Override
        public boolean isEmpty() {
            return front == tail;
        }
    
        //实现getSize方法
        @Override
        public int getSize() {
            return size;
        }
    
        //实现enqueue方法
        @Override
        public void enqueue(E e) {
            //判断队列是否已满
            if ((tail + 1) % data.length == front) {
                resize(getCapacity() * 2);
            }
    
            data[tail] = e;
            tail = (tail + 1) % data.length;
            size++;
        }
    
        //实现dequeue方法
        @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;
        }
    
        //实现getFront方法
        @Override
        public E getFront() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty.");
            }
            return data[front];
        }
    
        //实现resize方法
        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;
        }
    
        //方便打印测试
        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
            res.append(String.format("Queue: size=%d, capacity=%d
    ", 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();
        }
    
        //测试
        public static void main(String[] args) {
            LoopQueue<Integer> queue = new LoopQueue<>();
    
            // 测试入队
            for (int i = 0; i < 5; i++) {
                queue.enqueue(i);
            }
            System.out.println(queue);
    
            // 测试出队
            queue.dequeue();
            System.out.println(queue);
        }
    }

     

  • 输出结果:
  • Queue: size=5, capacity=10
    front [0, 1, 2, 3, 4] tail
    Queue: size=4, capacity=10
    front [1, 2, 3, 4] tail

     

5.. 循环队列的复杂度分析

  • LoopQueue<E>
    ·void enqueue(E)   O(1)    均摊
    ·E dequeue()   O(1)   均摊
    ·E getFront()  O(1)
    ·int getSize()   O(1)
    ·boolean isEmpty()   O(1)

     

6.. 使用简单算例测试ArrayQueue与LoopQueue的性能差异

  • import java.util.Random;
    
    public class Main {
    
        // 测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
        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<Integer> arrayQueue = new ArrayQueue<>();
            double time1 = testQueue(arrayQueue, opCount);
            System.out.println("ArrayQueue, time: " + time1 + " s");
    
            LoopQueue<Integer> loopQueue = new LoopQueue<>();
            double time2 = testQueue(loopQueue, opCount);
            System.out.println("LoopQueue, time: " + time2 + " s");
    
        }
    }

     

  • 输出结果
  • ArrayQueue, time: 2.88077896 s
    LoopQueue, time: 0.01140229 s

     



以上是关于第二十四篇 玩转数据结构——队列(Queue)的主要内容,如果未能解决你的问题,请参考以下文章

C++进阶第二十四篇——布隆过滤器(概念+实现+海量数据处理问题)

第二十四篇 jQuery 学习6 删除元素

第二十四篇:导出SOUI对象到LUA脚本

flask第二十四篇——模板自定义过滤器

C++进阶第二十四篇——布隆过滤器(概念+实现+海量数据处理问题)

Python之路第二十四篇:Python学习路径及练手项目合集