Data structure_3: Queue (array queue + circular queue)

Queue

Write at the beginning

  • First-in-first-out linear data structure (FIFO)
  • Only allow delete operations at the front of the table (front), and insert operations at the back of the table (tail). The end that performs the insertion operation is called the end of the team, and the end that performs the deletion operation is called the head of the team.
  • When there are no elements in the queue, it is called an empty queue.

The realization of array queue, combined with dynamic array, constructs ArrayQueue<E> by way of interface

  • Interface: Queue

      public interface Queue<E> {
    
          /**
           * 获取队列容量大小
           * @return
           */
          int getSize();
      
          /**
           * 队列空判断
           * @return
           */
          boolean isEmpty();
      
          /**
           * 入队
           * @param e
           */
          void enqueue(E e);
      
          /**
           * 出队
           * @return
           */
          E dequeue();
      
          /**
           * 获取队首元素
           * @return
           */
          E getFront();
      }
    
  • Interface implementation class: ArrayQueue<E>

      public class ArrayQueue<E> implements Queue<E>{
    
          private Array<E> array;
      
          public ArrayQueue() {
              array = new Array<>();
          }
      
          public ArrayQueue(int capacity) {
              array = new Array<>(capacity);
          }
      
          @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() {
              return "ArrayQueue{" +
                      "array=" + array +
                      '}';
          }
      }
    

Implementation of circular queue

  • Limitations of the array queue: focus on the dequeue operation, the time complexity is O(n) level
    • Why is it said that the dequeue operation is for the first element of the team, and the underlying array will move the remaining elements forward after removing the element with index 0, which involves traversal operations, so the time complexity rises to O(n).
    • Circular queue, abandon the forward operation of other elements after the element is dequeued, construct the head pointer front, tail pointer tail (essentially the size of the dynamic array), and simplify the forward movement of the dequeued element to the head pointer movement operation (front++).
  • Two points to note:
    • The circular queue is empty: front == tail [initial state]
    • The circular queue is judged to be full: (tail + 1)% C == front [C is the length of the queue, wasting an array space for the tail pointer to point to]
  • About the expansion of the underlying dynamic array?
    • In the dynamic array article, it is mentioned that the essence of expansion is to open up new memory space and copy the contents of the original array to the new array. There is a problem in this place. The circular array makes full use of the space of the array, so it is used as a circular queue. When it is full, the position where the array index is 0 is not necessarily the first element of the circular queue.
    • As shown in the figure below, the position where the array index is 0 is the last element added in the circular queue. At this moment, the array expansion operation is triggered. When copying the array, you need to pay attention: Since the queue is also a linear structure, the elements should be placed in order, so the resize method of the dynamic array Some changes need to be made.
      Insert picture description here
  • Transform ArrayQueue<E> and rewrite the method in combination with the Queue interface
    • Create LoopQueue<E>, complete the basic member attribute construction

        public class LoopQueue<E> implements Queue<E> {
      
            private E[] data;
            private int front, tail;
            private int size; // 队列实际容量标识
        
            public LoopQueue(int capacity) {
                // capacity + 1 适应循环队列满载机制
                // (tail + 1) % c == front
                data = (E[]) new Object[capacity + 1];
                front = 0;
                tail = 0;
                size = 0;
            }
        
            public LoopQueue() {
                this(10);
            }
        
        	// 获取队列最大容量
            public int getCapacity() {
                return data.length - 1;
            }
        }	
      
    • getSize() Get the actual capacity of the queue

        @Override
        public int getSize() {
            return size;
        }
      
    • isEmpty() Queue empty judgment

        @Override
        public boolean isEmpty() {
            // 队列判空条件
            return front == tail;
        }
      
    • getFront() Get the head element

        @Override
        public E getFront() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty");
            }
            return data[front];
        }
      
    • Rewrite resize(), regularize the circular queue

        /**
         * 容量重置
         * @param capacity
         */
        private void resize(int capacity) {
            E[] newData = (E[]) new Object[capacity + 1];
            for (int i = 0; i < size; i++) {
                // 新数组中的元素索引相较原数组中索引存在front的偏移量
                newData[i] = data[(front + i) % data.length];
            }
            // 数组地址指向、头指针变更为默认值、尾指针指向变更
            data = newData;
            front = 0;
            tail = size;
        }
      
    • enqueue(E e) 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() dequeue

        @Override
        public E dequeue() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty");
            }
            E res = data[front];
            data[front] = null;
            front  = (front + 1) % data.length;
            size --;
        	// 四等分点进行数组缩容,避免复杂度震荡
            if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
                resize(getCapacity() / 2);
            }
            return res;
        }
      

Comparison-array queue and circular queue (considered from the perspective of enqueue and dequeue respectively)

  • testing method

      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;
      }
    
  • Test the Main method, define the number of operations, create array queue and circular queue objects respectively

      public static void main(String[] args) {
          int opCount = 100000;
          Queue<Integer> arrayQueue = new ArrayQueue<>();
          Queue<Integer> loopQueue = new LoopQueue<>();
          System.out.println("arrayQueue:" + testQueue(arrayQueue, opCount) + " s");
          System.out.println("loopQueue:" + testQueue(loopQueue, opCount) + " s");
      }
    
  • Time-consuming test to join the team:
    Insert picture description here

  • Time-consuming test of entry + dequeue: The main difference between the two queues is dequeue. The complexity of the array queue increases because of the dequeue operation.
    Insert picture description here
    Summary: The result is obvious. The circular queue method makes reasonable use of the array space and dequeues the operation. The time complexity is back to O(1) level, which has better performance than the array queue.

Guess you like

Origin blog.csdn.net/Nerver_77/article/details/89787019