超全面Java中的队列(Queue)

目录

引言:敲门砖​编辑

认识Queue基础

Queue定义

Queue 使用 

这就是循环队列 ⬆️

 Queue 基本方法

模拟实现队列 

一:by 数组

二:by 单链表 

实现循环队列 :

双端队列 

创建

方法


引言:敲门砖

(👈图片from豆包)       排队在日常生活中难以避免,排队不插队成了当今社会的公序良俗,干饭人干饭魂,谁第一个冲到食堂,站在打饭窗口,谁就第一个能拿到饭后继的人排在其后,排成一列(先到先吃)。那在计算机中的队列呢?

认识Queue基础

                                                  from JDK 17 Documentation

Queue定义

队列(Queue)也是一个进行了进一步的封装和限制的线性表,提供了入队列、出队列、取队首元素。在 Java 中,Queue 是一个接口,它继承自 Collection 接口,通常遵循 FIFO(先进先出) 原则。

队列这样的数据结构,常用于辅助“耗时较长的”的操作、无法一次处理完的操作,而这就需要按照顺序,一个一个的进行处理,这时就可以将待处理的数据放到队列中,确保处理顺序不乱,先进的先处理。

Queue 使用 

由于Queue 是一个接口,不能创建实例只能通过创建其他类去实现该接口,并实现抽象方法。

Queue <Integer> queue = new LinkedList<>(); 

// 之所以 new LinkedList 是因为其实现了Queue 接口

Queue <Integer> queue = new ArrayDeque<>();

// 基于数组   更高效、元素个数上限可控  内存使用率更高

如上图 基于数组,初始时,head 和 tail 都指向 0 位置,此时认为是一个空队列。
随着元素入队[head,tail) 前闭后开,tail后移;元素出队,head后移 ,head++ 后其之前指向的元素就被视为无效了,逻辑删除了。

数组是有固定容量的,当tail 走到数组末尾是否意味着队列就满了呢?

给上图变形一下
 

首尾相接,当head 和 tail 重合时,队列就满了 。
但上面说过 初始时 head , tail 重合,队列为空
❓那怎么区分 满 or 空 

方案一:

牺牲一个格子,当 tail 走到 head 前面一个位置时就视作队列为满;

方案二:

引入变量 size 当 size == 数组长度时即为满;size == 0  空
入队 size++

出队 size--  

这就是循环队列 ⬆️

 在用代码实现循环队列前先认识一下队列的基本方法:

 Queue 基本方法
方法类别方法名功能描述队列空时队列满时
入队列add(E e)把元素添加到队列尾部抛出异常抛出异常
offer(E e)尝试将元素添加到队列尾部抛出异常返回 false
出队列remove()移除队首元素抛出异常抛出异常
poll()移除队首元素返回 null返回 null
查看队首元素element()查看队首,不取出抛出异常抛出异常
peek()查看队首,不取出返回 null返回 null

黄色的方法更常用

 还有 size()  获取队中元素个数
         isEmpty()   判空

模拟实现队列 
一:by 数组
package queue;

import java.util.Scanner;

public class MyQueueByArray {
    private String[] arr = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    public MyQueueByArray(){
        arr = new String[1000];
    }

    public MyQueueByArray(int capacity){
        arr = new String[capacity];
    }

    public void offer(String val){
        // 如果队列满了
        if(size == arr.length){
            // 可抛异常,可扩容(不建议)
            return;
        }
        // 把新的元素放到 tail 的位置上
        arr[tail] = val;
        // 更新 tail 的指向
        tail++;
        if(tail == arr.length){
            tail = 0;
        }

        // 更新 tail 的指向 的另一种写法
        // tail = (tail + 1) % arr.length;

        // 更新元素个数
        size++;
    }

    public String poll(){
        if(size == 0){
            return null;
        }
        // 取出队首元素,保存起来,以便返回
        String val = arr[head];
        head++;
        if(head == arr.length){
            head = 0;
        }
        size--;
        return val;
    }

    public String peek(){
        if(size == 0){
            return null;
        }
        return arr[head];
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public int size(){
        return size;
    }
}
二:by 单链表 
package queue;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;
// 基于单链表实现队列
public class MyQueue {
    // 链表的一个节点
    static class Node{
        public String val;
        public Node next;

        public Node(String val){
            this.val = val;
            this.next = null;
        }
    }
    // 把队列的头部和尾部都记录下来
    // 基于链表实现队列:
    // 这个做法 链表的头/尾与队列的头/尾是一致的
    // 1. 入队 -> 尾插
    // 2. 出队 -> 头删
    // 也可以
    // 1. 入队 -> 头插
    // 2. 出队 -> 尾删
    //

    private Node head = null;
    private Node tail = null;

    // 入队
    public void offer(String val){
        // 链表尾插
        Node newNode = new Node(val);
        // 链表为空
        if(head == null){
            head = newNode;
            tail = newNode;
            return;
        }
        // 非空
        tail.next = newNode;
        tail = newNode;

    }

    // 出队列
    public String poll(){
        // 头删
        if(head == null){
            return null;
        }
        // 保存头部节点的值
        // 接下来把这个节点删除掉,返回这个值
        String val = head.val;
        // 更新 head 的指向
        head = head.next;
        // 如果链表的节点数超出 1 个 ,删掉一个元素,不影响 tail 的指向
        // 但是,链表的节点数只有一个,删掉这个元素,tail 就应该指向 null
        if(head == null){
            tail = null;
        }
        return val;
    }

    // 取队首元素
    public String peek(){
        if(head == null){
            return null;
        }
        return head.val;
    }


   
}
实现循环队列 :
package queue;

import java.util.Scanner;

public class MyCirecleQueue {
    // 创建一个数组
    private int[] arr;
    private int elem = 0;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    // 构造方法
    public MyCirecleQueue(int cap){
        this.arr = new int[cap];
    }

    // 判空
    public boolean isEmpty() {
        return size == 0;
    }
    // 判满
    public boolean isFull() {
        return size == arr.length;
    }

    public int size() {
        return size;
    }

    // 入队
    public boolean enqueue(int elem){
        if(isFull()){
            return false;
        }
        arr[tail] = elem;
        tail = (tail + 1) % arr.length;   // 利用取模运算实现循环
        size++;
        return true;
    }

    // 出队
    public Integer dequeue() {
        if (isEmpty()) return null;
        int value = arr[head];
        head = (head + 1) % arr.length;
        size--;
        return value;
    }

    // 查看队首元素
    public Integer peek() {
        if (isEmpty()) return null;
        return arr[head];
    }
}

双端队列 

Deque: 两端都可以进⾏⼊队和出队操作的队列。上图

Deque 就不用遵守先进先出的规则了 ,更加灵活。同样其也是一个接口不能创建实例。

创建

Deque<Integer> deque = new ArrayDeque<>();

Deque<Integer> deque = new LinkedList<>();

方法

addFirst()         // 从队首入队列

addLast()         // 从队尾入队列

removeFirst()   // 从队首出队列

removeLast()   // 从队尾出队列

addFirst()  +  removeFirst()   相当于栈

addLast()  + removeLast()    相当于队列

 队列练习

 1.用队列实现栈

leetcode 225 用队列实现栈

思路:1.准备A,B 两个队列
           2.入栈:往 A 中 入队列  
           3.出栈:先把A中的所有元素循环出队列,并入队列到 B 中,当A中仅剩最后一个元素时,这个元素就是栈顶元素,删除掉。
           完成一次出栈,元素都到 B 中了,方便后续入栈,交换A,B 指向
           4.取栈顶:把A中元素往B中放,仅剩最后一个元素时返回其值。

A 为主 B 为辅 辅助删除元素

class MyStack {
    private Queue<Integer> A = new LinkedList<>();
    private Queue<Integer> B = new LinkedList<>();

    public MyStack() {
        //上面初始化过了,空着即可
    }

    // 交换 A B 指向
    public void swap(){
        Queue<Integer> temp = new LinkedList<>();
        temp = A;
        A = B;
        B = temp;
    }
    
    public void push(int x) {
        A.offer(x);
    }
    
    public int pop() {
        while(A.size() > 1){
            B.offer(A.poll());
        }
        int result = A.poll();
        swap();
        return result;
    }
    
    public int top() {
        while(A.size() > 1){
            B.offer(A.poll());
            }
            int result = A.poll();
            B.offer(result);
            swap();
            return result;
    }
    
    public boolean empty() {
        return A.isEmpty();
    }
}
2.用栈实现队列 

 

思路:准备两个栈
           A专门负责入队列 B专门负责出队列
           入队列,把所有B中的元素出栈 入栈到A
           出队列,把所有A中的元素出站,入站到B,从B中出战
           取栈顶元素,把A的元素全部转移到B中,取B的栈顶。
           判定为空,确保两个站都不空,此时整体就是非空

class MyQueue {
    private Stack<Integer> A = new Stack<>();  // 用于入队操作的栈A
    private Stack<Integer> B = new Stack<>(); // 用于出队操作的栈B

    public MyQueue() {
        
    }
    
    public void push(int x) {
        A.push(x);
    }
    
    public int pop() {
        if (B.empty()) {
            while (!A.empty()) {
                B.push(A.pop());
            }
        }
        return B.pop();
    }
    
    public int peek() {
        if (B.empty()) {
            while (!A.empty()) {
                B.push(A.pop());
            }
        }
        return B.peek();
    }
    
    public boolean empty() {
        return A.empty() && B.empty();
    }
}

头脑🌪️   上述两问是否能用双端队列解决呢 ?

人生偶尔会走上一条陌路 像是没有指标的地图 别让她们说你该知足 只有你知道什么是你的幸福
                                                                                                                    ————  二十二 DT
                                                                  👍⭐️🦀🦀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值