【DS】 Queue 基本用法

在这里插入图片描述

当年面试问的最多的那批 Java 集合框架现在问的还多么? 编程十年(手动狗头) , 我把这些集合框架做成盲盒, 无论你多难, 我都想去了解你, 今天来看-----pia~, Queue,一个接口,代表着一种先进先出(FIFO)的数据结构,适用于存储元素并管理它们的顺序。常用的实现类包括LinkedList和PriorityQueue。它支持元素的插入、删除和检索操作,是在多线程环境下进行安全操作的重要工具。

一. 概念

队列: 只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(FirstIn First Out) 
入队列: 进行插入操作的一端称为队(Tail/Rear) 
出队列: 进行删除操作的一端称为队头(Head/Front)

在这里插入图片描述

二. 队列的使用

在Java中,Queue是个接口,底层是通过链表实现的。

在这里插入图片描述

在这里插入图片描述

注意: Queue是个接口,在实例化时必须实例化子类对象。

    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());
        }
    }

三. 队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构 和 链式结构。同学们思考下:队列的实现使用顺序结构还是链式结构好?

链式结构更好: 因为链式结构可以记录队头和队尾, 入队和出队的时间复杂度都是 O(1), 而 对于顺序结构的话出队时需要挪动元素, 时间复杂度为 O (N)

在这里插入图片描述

class Queue {
    // 双向链表节点
    public static class ListNode{
        ListNode next;
        ListNode prev;
        int value;
        ListNode(int value){
            this.value = value;
        }
    }
    ListNode first; // 队头
    ListNode last; // 队尾
    int size = 0;
    // 入队列---向双向链表位置插入新节点
    public void offer(int e){
        ListNode newNode = new ListNode(e);
        if(first == null){
            first = newNode;
            // last = newNode;
        }else{
            last.next = newNode;
            newNode.prev = last;
            // last = newNode;
        }
        last = newNode;
        size++;
    }
    
    // 出队列---将双向链表第一个节点删除掉
    public int poll() throws Exception {
        // 1. 队列为空
        // 2. 队列中只有一个元素----链表中只有一个节点---直接删除
        // 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
        int value = 0;
        if(first == null){
            throw new Exception("队列为空");
        }else if(first == last){
            last = null;
            first = null;
        }else{
            value = first.value;
            first = first.next;
            first.prev.next = null;
            first.prev = null;
        }
        --size;
        return value;
    }
    
    // 获取队头元素---获取链表中第一个节点的值域
    public int peek() throws Exception {
        if(first == null){
            throw new Exception("队列为空");
        }
        return first.value;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty(){
        return first == null;
    }
}

链表实现队列: 需要记录队头和队尾

四. 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统中生产者消费者模型时可以就会使用循环队列。循环队列通常使用数组实现。

在这里插入图片描述

数组下标循环的小技巧 :

  1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length (offset 是要移动的步数)

在这里插入图片描述

  1. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length (offset 是要移动的步数)

在这里插入图片描述

如何区分空与满 :

  1. 通过添加 size 属性记录
    每入队一个元素 size++, 每出队一个元素 size–
  2. 保留一个位置 (多开辟一块空间)
    队空时: front == rear, 队满时 front == rear + 1
  3. 使用标记
    每删除一个元素 flag 设置为 false, 每增加一个元素 flag 设置为 true

设计一个循环队列

OJ链接

class MyCircularQueue {

    private int[]elem;
    private int head;
    private int tail;
    public MyCircularQueue(int k) {
        //多开一个空间用来区分队列是空还是满
        this.elem=new int[k+1];
    }

    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        this.elem[tail]=value;
        //tail为length-1
        tail=(tail+1)%elem.length;
        return true;
    }

    public boolean isFull(){
        if((tail+1)%elem.length==head){
            return true;
        }
        return false;
    }
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        head=(head+1)%elem.length;
        return true;
    }

    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return elem[head];
    }

    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        //注意tail为0的情况
        return elem[(tail+elem.length-1)%elem.length];
    }

    public boolean isEmpty() {
        return head==tail;
    }
}

五. 双端队列 (Deque)

双端队列(deque): 是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

在这里插入图片描述

Deque是一个接口,使用时必须创建LinkedList的对象。

在这里插入图片描述

在实际使用中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

六. 面试题

1. 用队列实现栈。

OJ链接

思路:
使用两个队列, 其中一个队列作为出栈队列, 另一个作为入栈队列, 当需要出栈时, 将入栈队列中的元素导入到出栈队列中, 除了最后一个元素,此时入栈队列就剩一个元素, 这个元素就是最新入栈的元素, 这样就能进行 peek 和 pop, 操作, 此时之前的入栈队列就变成了出栈队列, 而出栈队列变成了入栈队列 . 如此循环往复

在这里插入图片描述

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}

2. 用栈实现队列。

OJ链接

思路:
将一个栈当作输入栈,用于压入push传入的数据;另一个栈当作输出栈,用于 pop 和 peek操作。

每次 pop或peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。

class MyQueue {
    Deque<Integer> inStack;
    Deque<Integer> outStack;

    public MyQueue() {
        inStack = new ArrayDeque<Integer>();
        outStack = new ArrayDeque<Integer>();
    }

    public void push(int x) {
        inStack.push(x);
    }

    public int pop() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.pop();
    }

    public int peek() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.peek();
    }

    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    private void in2out() {
        while (!inStack.isEmpty()) {
            outStack.push(inStack.pop());
        }
    }
}

以上就是对 Queue 的基本讲解, 希望能帮到你 !
评论区欢迎指正 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值