[Data structure] Use of queues | Simulation implementation | Circular queue | Double-ended queue | Interview questions

1. Queue

1.1 Concept

Queue : A special linear table that only allows data insertion at one end and deletion at the other end. The queue has a first-in-first-out FIFO (First In First Out). Queue: The end where the insertion operation is performed is called the tail/ Rear) out of the queue: the end that performs the deletion operation is called the head/front of the queue (Head/Front)

The difference between a queue and a stack: a queue is first in, first out (the tail of the queue goes in, the head of the queue goes out) , while a stack is first in, first out.


1.2 Use of queues

In Java, Queue is an interface, and the bottom layer is implemented through a linked list .

method Function
boolean offer(E e) Queue
E poll() Dequeue
peek() Get the head element
int size() Get the number of valid elements in the queue
boolean isEmpty() Check if the queue is empty

Note: Queue is an interface, and the LinkedList object must be instantiated when instantiating it , because LinkedList implements the Queue interface Queue<Integer> q = new LinkedList<>();

 Queue has the following six methods. Each two has the same function (add | delete | search), but there are still certain differences.

difference:

(1) add, remove, and element are all common methods of Collection

        Offer, poll, and peek are queue-specific methods.

(2) Some of the common methods implemented by Collection will report exceptions.

        Exceptions will not be reported in queue-specific methods. 

        eg: If you want to add a new item to a full queue, calling the add() method will throw an unchecked exception, and calling the offer() method will return false.

public static void main(String[] args) {
    Queue<Integer> q = new LinkedList<>();
    q.offer(1);
    q.offer(2);
    q.offer(3);
    q.offer(4);
    q.offer(5); // 从队尾入队列
    System.out.println(q.size());
    System.out.println(q.peek()); // 获取队头元素

    q.poll();
    System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回

    if(q.isEmpty()){
        System.out.println("队列空");
    } else {
        System.out.println(q.size());
    }
}

1.3 Queue simulation implementation

Since elements can be stored in the queue, the bottom layer must have space that can store elements. Through the previous study of linear tables, we learned that there are two common space types: sequential structure and chain structure . Students, think about this: Is it better to use a sequential structure or a chain structure to implement a queue?

(1) Single linked list simulation implementation: In order to ensure that the time complexity is O(1)

It is definitely not possible to enter from the head of the queue and exit from the tail, so the time complexity of deleting the tail node is O(N)

Therefore, we use the method of entering from the end of the queue and exiting from the head of the queue. However, at this time, the last pointer is needed to point to the end of the queue. Only by inserting the tail and deleting the head can the conditions be met.

(2) Doubly linked list simulation implementation: It is possible whether entering from the head of the team and exiting from the tail, or entering from the tail and exiting from the head.

The doubly linked list is a god-like existence. LinkedList : it can be used as a doubly linked list, stack, or queue.

 (3) Array simulation implementation: It can be used to implement a circular queue. If a normal queue is implemented, there will be an element in front of the deleted element (in front of the front pointer) that cannot be added.

The following code implements a queue using a doubly linked list (tail insertion, head deletion):

import java.security.PublicKey;
//双向链表(尾插,头删)实现队列
public class MyQueue {
    static class ListNode {
        public int val;
        public ListNode prev;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }

    }
    public ListNode head;
    public ListNode last;

    //入队
    public void offer(int val) {
        ListNode node = new ListNode(val);
        //尾插
        if(head == null) {
            head = last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

    //出队
    public int poll() {
        //空队列
        if(head == null) {
            return -1;
        }
        //一个节点
        int val = -1;
        if (head.next == null) {
            val = head.val;
            head = null;
            last = null;
            return val;
        }
        val = head.val;
        head = head.next;
        head.prev = null;
        return val;
    }

    //判断是否为空
    public boolean empty() {
        return head == null;
    }

    public int peek() {
        if (head == null) {
            return -1;
        }
        return head.val;
    }
}

1.4 Circular Queue

In practice, we sometimes use a queue called a circular queue ( it is a graph rather than a linear structure, but because its name is called a circular queue, it is not called a directed graph). For example, when the operating system course explains the producer-consumer model, circular queues can be used. Ring queues are usually implemented using arrays

 Tips for array subscript looping

1. The index is at the end and further back (offset is less than array.length): index = (index + offset) % array.length

 2. The index is the first and then forward (offset is less than array.length): index = (index + array.length - offset) % array.length

How to distinguish between empty and full

1. Judge by adding size count (this is simpler)

2. Reserve a position and waste space to indicate fullness (this is used more often)

3. Use the mark boolean flg (mark it as false at the same position before starting, mark it as true when meeting again) (this is also relatively simple)

Design circular queue

class MyCircularQueue {
    public int[] elem;
    public int front;
    public int rear;

    public MyCircularQueue(int k) {
        elem = new int[k + 1];// 浪费一个空间,也就是需要多开一个元素的空间
    }

    // 入队操作
    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        elem[rear] = value;
        rear = (rear + 1) % elem.length;// 注意点1:不可直接rear+1
        return true;
    }

    // 删除队头元素(空不空 + front移动)
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % elem.length; // 注意点2:不可直接front+1
        return true;
    }

    // 得到队头元素 不删除
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }

    // 得到队尾元素 不删除
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        // rear=0说明刚刚走过一圈以上,那么队尾就为elem.length-1
        // rear!=0说明还没到跨越的位置,直接-1即可
        int index = (rear == 0) ? elem.length - 1 : rear - 1;
        return elem[index];
    }

    // 判空 front和rear都在起始点
    public boolean isEmpty() {
        return front == rear;
    }

    public boolean isFull() {
        return (rear + 1) % elem.length == front;
    }
}

1.5 Double-ended queue (Deque)

A double-ended queue (deque) refers to a queue that allows both ends to perform enqueue and dequeue operations . Deque is the abbreviation of "double ended queue". That means that elements can be dequeued and entered from the head of the team, and can also be dequeued and entered from the end of the team.

 Deque is an interface. When using it, you must create a LinkedList object (so it can be used as a doubly linked list | stack)

In actual projects, the Deque interface is commonly used. Both stacks and queues can use this interface.

Deque<Integer> stack = new ArrayDeque <>(); //Linear implementation of double-ended queue

Deque<Integer> queue = new LinkedList <>(); //Chained implementation of double-ended queue

 Stack is not the only class. It can be a stack, a LinkedList, or an ArrayDeque.


2. Interview questions

1. Use queues to implement stacks

 Thinking: Can an ordinary queue implement a stack? It's definitely not possible, one is first in, first out, and the other is first in, last out.

Idea :

(1) When both queues are empty, put them in the first queue

(2) When "pushing" again, put it into a queue that is not empty.

(3) When "popping", pop out the non-empty queue, pop out size -1 elements, and the remaining elements are the elements to be popped out of the stack.

class MyStack {
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;

    public MyStack() {
        qu1 = new LinkedList();
        qu2 = new LinkedList();

    }
    
    public void push(int x) {
        //当两个队列都是空的时候放到第一个队列
        if(empty()) {
            qu1.offer(x);
            return;
        } 
        //入栈,放到不为空的队列
        if(!qu1.isEmpty()) {
            qu1.offer(x);
        } else {
            qu2.offer(x);
        }
        
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        } 
        //找到不为空的队列 ,出size-1个元素
        if(!qu1.isEmpty()) {
            int size = qu1.size();
            for(int i =0;i<size-1;i++) { //这里不能写成i<qu1.size(),因为qu1.size()一直在变
                //出size-1个元素都放到另一个队列
                qu2.offer(qu1.poll());
            }
            //最后出本队列的最后一个元素
            return qu1.poll();
        } else {
           int size = qu2.size();
            for(int i =0;i<size-1;i++) {
                qu1.offer(qu2.poll());
            }
            return qu2.poll();
        }
    }
    
    public int top() {
        if(empty()) {
            return -1;
        } 
        //找到不为空的队列 ,出size个元素
        if(!qu1.isEmpty()) {
            int size = qu1.size();
            int tmp = -1;
            for(int i =0;i<size;i++) { 
                //出size个元素都放到另一个队列,并用tmp记录下这个数
                tmp = qu1.poll();
                qu2.offer(tmp);
            }
            //返回最后出本队列的元素
            return tmp;
        } else {
          int size = qu2.size();
            int tmp = -1;
            for(int i =0;i<size;i++) { 
                tmp = qu2.poll();
                qu1.offer(tmp);
            }
            return tmp;
        }
    }
    
    //两个队列都是空代表栈为空
    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

2. Use stack to implement queue

Idea:

(1) "Enqueue": Put the data into the first stack

(2) "Dequeue": Just remove the top element of the stack in s2. If s2 is empty, put all the elements in s1 into s2.

(3) When both stacks are empty, it means that the simulated queue is empty. 

class MyQueue {

    private Stack<Integer> s1;
    private Stack<Integer> s2;

    public MyQueue() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    public void push(int x) {
        s1.push(x);
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        }
        if(s2.isEmpty()) {
            //s2为空,把s1中所有的元素放入s2中
        while(!s1.isEmpty()) {
            s2.push(s1.pop());
            }
        }
        return s2.pop();

    }
    
    public int peek() {
        if(empty()) {
            return -1;
        }
        if(s2.isEmpty()) {
            //s2为空,把s1中所有的元素放入s2中
        while(!s1.isEmpty()) {
            s2.push(s1.pop());
            }
        }
        return s2.peek();
    }
    
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}

Guess you like

Origin blog.csdn.net/qq_73017178/article/details/135094375