美文网首页
线性结构--队列

线性结构--队列

作者: 二妹是只猫 | 来源:发表于2019-06-28 13:43 被阅读0次

    队列和栈其实很类似,最大的不同就是栈是先将后出,而队列是先进先出,就像一根水管固定一端进,而另一端负责出
    队列又分为数组队列和循环队列,数组队列dequeue(出队)方法的时间复杂度为O(n),而循环队列很好的优化了这点,下面通过代码来看看它们是如何实现的.

    数组队列(ArrayQueue)

    接口queue:

    public interface Queue<E> {
        //获取大小
        int getSize();
        //是否为空
        boolean isEmpty();
        //添加数据
        void enqueue(E e);
        //取出数据
        E dequeue();
        //获取第一个数据
        E getFront();
    }
    

    实现:ArrayQueue

    public class ArrayQueue<E> implements Queue<E> {
        private Array<E>  array;
    
        public ArrayQueue(int capacity){
            array = new Array(capacity);
        }
    
        public ArrayQueue(){
            this(10);
        }
        @Override
        public int getSize() {
            return array.getSize();
        }
    
        @Override
        public boolean isEmpty() {
            return array.isEmpty();
        }
    
        @Override
        public void enqueue(E e) {
            array.addLast(e);
        }
    
        @Override
        public E dequeue() {
            return array.removeFirst();
        }
    
        @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("]");
            return res.toString();
        }
    }
    

    测试:

    循环10次入队,每过3次出队

      public static void main(String[] args) {
          ArrayQueue<Integer> queue = new ArrayQueue<>();
          for(int i = 0 ; i < 10 ; i ++){
              queue.enqueue(i);
              System.out.println(queue);
              if(i % 3 == 2){
                  queue.dequeue();
                  System.out.println(queue);
              }
          }
      }
    

    结果打印

    Queue: front [0]
    Queue: front [0, 1]
    Queue: front [0, 1, 2]
    Queue: front [1, 2]
    Queue: front [1, 2, 3]
    Queue: front [1, 2, 3, 4]
    Queue: front [1, 2, 3, 4, 5]
    Queue: front [2, 3, 4, 5]
    Queue: front [2, 3, 4, 5, 6]
    Queue: front [2, 3, 4, 5, 6, 7]
    Queue: front [2, 3, 4, 5, 6, 7, 8]
    Queue: front [3, 4, 5, 6, 7, 8]
    Queue: front [3, 4, 5, 6, 7, 8, 9]
    
    Process finished with exit code 0
    

    循环队列(LoopQueue)

    LoopQueue
    如图所示,此时队列虽然有两次出队操作是坐标0和1此时为空,但按照数组队列来说,新入队的h仍会判断此时队列已满--造成扩容,是原本时间复杂度为O(1)的入队操作变回O(n),而循环队列将我们最后一位(tail)直接指向了坐标0,这样就既可以利用前面出队后空出来的0地址又减少了扩容操作,这就是循环队列.

    实现LoopQueue:

    基础部分:

    public class LoopQueue<E> implements Queue<E> {
        private int size;
        private int tail;
        private int front;
        private E[] data;
    
        public LoopQueue(int capacity){
            data = (E[]) new Object[capacity+1];
            front = 0;
            tail = 0;
            size = 0;
        }
    
        public LoopQueue(){
            this(10);
        }
        public int getCapacity(){
            return data.length - 1;
        }
        @Override
        public int getSize() {
            return size;
        }
    
        @Override
        public boolean isEmpty() {
            return front==size;
        }
        @Override
        public E getFront() {
            return data[front];
        }
    }
    

    这里主要需要注意的由于tail是指向队尾的下一个位置,所以在初始化时数组的size+1,相应的返回的队列长度时也需要-1

    动态扩容:

        private void resize(int capacity) {
            E[] newData = (E[]) new Object[capacity];
            for (int i = 0; i < size; i++) {
                newData[i] = data[((i+front)%data.length)];
            }
            data = newData;
            tail = size;
            front = 0;
        }
    

    这里注意在新的队列中需要将旧的数据从头开始排列(data[((i+front)%data.length)])
    入队和出队:

        @Override
        public void enqueue(E e) {
            if ((tail+1)%data.length==front) {
                resize(getCapacity()*2);
            }
            data[tail] = e;
            tail = (tail+1)%data.length;
            size++;
        }
    
        private void resize(int capacity) {
            E[] newData = (E[]) new Object[capacity];
            for (int i = 0; i < size; i++) {
                newData[i] = data[((i+front)%data.length)];
            }
            data = newData;
            tail = size;
            front = 0;
        }
    
        @Override
        public E dequeue() {
            if(isEmpty())
                throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
    
            E e = data[front];
            data[front] = null;
            front = (front+1)%data.length;
            size--;
            if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
                resize(getCapacity() / 2);
            return e;
        }
    
    

    测试:

        @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();
        }
    
        public static void main(String[] args){
    
            LoopQueue<Integer> queue = new LoopQueue<>(5);
            for(int i = 0 ; i < 10 ; i ++){
                queue.enqueue(i);
                System.out.println(queue);
    
                if(i % 3 == 2){
                    queue.dequeue();
                    System.out.println(queue);
                }
            }
        }
    

    输出:

    Queue: size = 1 , capacity = 5
    front [0] tail
    Queue: size = 2 , capacity = 5
    front [0, 1] tail
    Queue: size = 3 , capacity = 5
    front [0, 1, 2] tail
    Queue: size = 2 , capacity = 5
    front [1, 2] tail
    Queue: size = 3 , capacity = 5
    front [1, 2, 3] tail
    Queue: size = 4 , capacity = 5
    front [1, 2, 3, 4] tail
    Queue: size = 5 , capacity = 5
    front [1, 2, 3, 4, 5] tail
    Queue: size = 4 , capacity = 5
    front [2, 3, 4, 5] tail
    Queue: size = 5 , capacity = 5
    front [2, 3, 4, 5, 6] tail
    Queue: size = 6 , capacity = 10
    front [2, 3, 4, 5, 6, 7] tail
    Queue: size = 7 , capacity = 10
    front [2, 3, 4, 5, 6, 7, 8] tail
    Queue: size = 6 , capacity = 10
    front [3, 4, 5, 6, 7, 8] tail
    Queue: size = 7 , capacity = 10
    front [3, 4, 5, 6, 7, 8, 9] tail
    
    Process finished with exit code 0
    

    动态数组和循环数组效率比较

    public class Main {
    
    
        //测试使用q出队入队opCount次,所需要的时间,单位:秒
        private static Double testQueue(Queue<Integer> queue, int opCount){
            long startTime = System.currentTimeMillis();
            Random random = new Random();
            for (int i = 0; i < opCount; i++) {
                queue.enqueue(random.nextInt(Integer.MAX_VALUE));
            }
    
            for (int i = 0; i < opCount; i++) {
                queue.dequeue();
            }
    
            long finishTime = System.currentTimeMillis();
            return (finishTime-startTime)/1000.0;
        }
        public static void main(String[] args) {
            int opCount = 100000;
            ArrayQueue arrayQueue = new ArrayQueue();
            System.out.println("arrayQueue, time = " +testQueue(arrayQueue,opCount));
    
    
            LoopQueue loopQueue = new LoopQueue();
            System.out.println("loopQueue, time = " +testQueue(loopQueue,opCount));
        }
    }
    

    结果:

    arrayQueue, time = 6.641
    loopQueue, time = 0.01
    
    Process finished with exit code 0
    

    可以看出循环队列的效率大大强于普通的数组队列

    相关文章

      网友评论

          本文标题:线性结构--队列

          本文链接:https://www.haomeiwen.com/subject/bjmlcctx.html