当前位置: 首页 > news >正文

Java队列(从内容结构到经典练习一步到位)

提示:宝藏博主,赶紧码住(づ ̄3 ̄)づ╭❤~

Java队列

    • 1.队列
    • 2. 核心接口
    • 3. 模拟实现
      • 3.1 链式实现
      • 3.2 线性实现
    • 4. 子接口
    • 5. 常见非并发实现类
      • 5.1 LinkedLsit
      • 5.2 ArrayDeque
      • 5.3 PriorityQueue
    • 6. 典型题目讲解
      • 6.1 用队列实现栈
      • 6.2 用栈实现队列


1.队列

队列是一种非常重要的数据结构,遵循先进先出原则.只允许在一端进行插入,另一端进行删除,插入的一端称为队尾,删除的一端是对头.

生活中排队买东西就是这个理,先到的先服务,先离开:

在这里插入图片描述

队列的两个基本操作是:

  • 入队(Enqueue):将元素添加到队列的尾部
  • 出队(Dequeue):从队列的头部移除并返回元素

2. 核心接口

Java中的队列主要通过java.util.Queue接口来实现,该接口扩展了java.util.Collection接口,并添加了一些方法,通过源码查看:

在这里插入图片描述

即:

操作类型方法描述失败时的行为
插入boolean offer(E e)将元素插入队列尾部若队列已满,返回false
boolean add(E e)若队列已满,抛出异常
移除E poll()移除对头元素并返回若队列为空,返回false
E remove()若队列为空,抛出异常
查看E peek()获取对头元素并返回若队列为空,返回false
E element()若队列为空,抛出异常

通过上述梳理很容易看出,上述6个方法其实只有3种不同作用,可以分为两组:

  • offer(),poll(),peek()是一组,通过返回值来指示操作成功与否,而不是异常,代码不易中断
  • add(),remove(),element()是一组,通过抛出异常来说明操作是否成功

offer(),poll(),peek()用得更多~

3. 模拟实现

主要针对offer(),poll(),peek(),isEmpty()方法

3.1 链式实现

接下来,我们使用双向链表模拟实现一下:

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 tail;//尾插法public void offer(int val) {ListNode node=new ListNode(val);if(head==null) {head=tail=node;}else {tail.next=node;node.prev=tail;tail=node;}}//头删public int poll() {if(head==null) return -1;//当然这里抛出一个异常更合适(我偷懒了),因为head的值也有可能为-1int ret=head.val;if(head.next==null) {head=null;tail=null;}else {head=head.next;head.prev=null;}return ret;}//获取队首元素public int peek() {if(head==null) return -1;//当然这里抛出一个异常更合适(我偷懒了),因为head的值也有可能为-1return head.val;}//判断队列是否为空public boolean isEmpty() {return head==null;}}

3.2 线性实现

上面3.1是用LinkedList取实现的队列,用数组也能实现,并且这个数组还有一个专门的名字,叫:循环队列

循环队列(Circular Queue)是一种固定大小数组实现的队列.

当你试图用普通数组实现一个队列时:

在这里插入图片描述

如图,定义两个指针first,last分别指向头和尾,当不断进行尾插头删操作后,first和last都不断向后移动,first前面会出现一片空闲位置,但是此时last已经走到了数组末端,无法添加新元素了,这种现象也叫"假溢出".

循环队列的出现解决了"假溢出"的问题:逻辑上将数组的首尾相连形成一个环,当指针到达数组末尾时,可以重新回到开头使用刚才抛出元素留下的空间.

在这里插入图片描述

但是似乎还是存在问题:如何判断循环队列是满的还是空的?

为什么这么问,因为在一开始,队列中没有元素,first和last指向同一个位置;而当不断尾插元素,last一度超过了first,然后将越来越靠近first,最后两者会相遇,仍然指向同一位置,如果数组把这次的相遇和一开始当做同样的情况处理,可不就出问题了.

对此,我们提出来一个解决方案:牺牲一个空间,这个空间不存储元素,而是作为数组将满的预警:

在这里插入图片描述

这个空间是动态的,隐藏在last的下一个位置,当last的下一个位置就是first时,就意味着数组满了.

还有一个问题,如何处理fisrt和last的遍历条件?出队时first++,入队时last++吗?

但是一直++越界了咋整?

对此,伟大的计算机科学家们想到了取模运算:

first=(first+1)%array.length;
last=(last+1)%array.length;

在这里插入图片描述

如上图的情况,计算last的下一个位置,使用last++时便会造成数组越界,而使用(7+1)%8=0,便可让last回到原数组的开头,进而实现循环遍历的效果.

接下来,通过一道编程题将理论转化为代码:

622. 设计循环队列 - 力扣(LeetCode)

class MyCircularQueue {public int[] elem;public int first;public int last;public MyCircularQueue(int k) {this.elem=new int[k+1];}//入队public boolean enQueue(int value) {//先判断满了没有if(isFull()) return false;//满了,入队失败elem[last]=value;last=(last+1)%elem.length;return true;}//出队public boolean deQueue() {//先判断是否为空if(isEmpty()) return false;//队列为空,出队失败first=(first+1)%elem.length;return true;}public int Front() {if(isEmpty()) return -1;return elem[first];}public int Rear() {if(isEmpty()) return -1;//返回last的前一个元素,要对last=0的情况进行特别处理int index=(last==0)?elem.length-1:last-1;return elem[index];}public boolean isEmpty() {return last==first;}public boolean isFull() {return (last+1)%elem.length==first;//判断last逻辑上的下一个元素与first的关系}
}

4. 子接口

双端队列Deque(Double Ended Queue)扩展了Queue,允许在头部和尾部进行插入和删除操作,这意味着它可以作为:

  1. 队列:使用offerLast()(尾删)和pollFirst()(头插)
  2. :使用push()/addFirstpop()/removeFirst()
  3. 双端队列:在两头进行操作

5. 常见非并发实现类

在这里插入图片描述

从上述Java集合框架示意图中可以清晰看到,LinkedList,ArrayDeque,PriorityQueue这三个类都实现了Queue接口,其中LinkedList,ArrayDeque还实现了其子接口Deque.

5.1 LinkedLsit

LinkedList是用链表实现的队列,插入和删除效率高.

import java.util.LinkedList;
import java.util.Queue;public class LinkedListQueueDemo {public static void main(String[] args) {Queue<String> queue = new LinkedList<>();// 入队queue.offer("A");queue.offer("B");queue.offer("C");// 查看队首元素(不移除)System.out.println("队首元素: " + queue.peek()); // A// 出队System.out.println("出队: " + queue.poll()); // ASystem.out.println("出队: " + queue.poll()); // BSystem.out.println("出队: " + queue.poll()); // CSystem.out.println("出队: " + queue.poll()); // null(队空)}
}

5.2 ArrayDeque

ArrayDeque基于动态数组实现,通常比LinkedList更快,因为内存连续,缓存友好.

import java.util.ArrayDeque;
import java.util.Queue;public class ArrayDequeQueueDemo {public static void main(String[] args) {Queue<String> queue = new ArrayDeque<>();// 入队queue.offer("A");queue.offer("B");queue.offer("C");// 查看队首元素System.out.println("队首元素: " + queue.peek()); // A// 出队System.out.println("出队: " + queue.poll()); // ASystem.out.println("出队: " + queue.poll()); // BSystem.out.println("出队: " + queue.poll()); // C}
}

5.3 PriorityQueue

PriorityQueue可以按照元素的优先级实现队列.

import java.util.PriorityQueue;
import java.util.Queue;public class PriorityQueueDemo {public static void main(String[] args) {Queue<Integer> queue = new PriorityQueue<>();// 入队(无序插入)queue.offer(30);queue.offer(10);queue.offer(20);// 注意:PriorityQueue 是按优先级排序的(默认自然顺序:小的优先)System.out.println("队首元素: " + queue.peek()); // 10// 出队(自动按优先级取出最小值)System.out.println("出队: " + queue.poll()); // 10System.out.println("出队: " + queue.poll()); // 20System.out.println("出队: " + queue.poll()); // 30}
}

6. 典型题目讲解

6.1 用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

用一个队列能实现栈吗?不能,两者的原理在某种程度来说是相反的,一个先进先出,而另一个确实先进后出.

因此需要准备两个队列:

在这里插入图片描述

要取出栈顶元素34,但是它却是队列中最后一个弹出的,因此需要从第一个队列queue1中出队n-1个元素到第二个空的队列queue2中,最后queue1剩下的那个就是要弹出的栈顶元素.

peek操作也是这么个理,稍微有一点不同:将queue1的元素全部弹出给queue2,在此过程中,定义一个变量tmp存储一下刚才弹出的,当queue1空了的时候,tmp就是要peek的元素了.

在这里插入图片描述

后面再push时,我们只需要将元素放入不为空的那个队列即可,若是第一次放呢?那就规定一下,放进第一个队列queue1中.

理论成立,开始coding

class MyStack {public Queue<Integer> queue1;public Queue<Integer> queue2;public MyStack() {queue1=new LinkedList<>();queue2=new LinkedList<>();}public void push(int x) {if(empty()) {//两队列都为空时默认放入queue1中queue1.offer(x);return;} if(!queue1.isEmpty()) {   //不是都为空的话,往不空的那个放queue1.offer(x);}else {queue2.offer(x);}}public int pop() {if(empty()) return -1;if(!queue1.isEmpty()) {int size=queue1.size();//注意队列的size是会变的,这里要定义一个变量将其存储起来for(int i=0;i<size-1;i++) {queue2.offer(queue1.poll());}return queue1.poll();}else {int size=queue2.size();//注意队列的size是会变的,这里要定义一个变量将其存储起来for(int i=0;i<size-1;i++) {queue1.offer(queue2.poll());}return queue2.poll();}}public int top() {if(empty()) return -1;if(!queue1.isEmpty()) {int ret=0;int size=queue1.size();//注意队列的size是会变的,这里要定义一个变量将其存储起来for(int i=0;i<size;i++) {ret=queue1.poll();queue2.offer(ret);}return ret;}else {int ret=0;int size=queue2.size();//注意队列的size是会变的,这里要定义一个变量将其存储起来for(int i=0;i<size;i++) {ret=queue2.poll();queue1.offer(ret);}return ret;}}public boolean empty() {return queue1.isEmpty() && queue2.isEmpty();//当两个队列都为空时才返回true}
}

6.2 用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

首先,还是那个问题,一个栈能实现队列吗?

也还是那个回答,不能.

并且思路也和上道题不一样,stack同一端进出,从stack1弹出到stack2,则两个栈中的元素顺序就完全颠了个个,队列则不一样,从一个队列弹出元素到另一个队列或者栈,两者的元素顺序是完全相同的.

在这里插入图片描述

如图,queue和stack1中元素压入顺序是完全相同的,弹出顺序则是完全相反的,那么要想得到完全一样的弹出顺序,只消把stack1中的元素全部压入stack2中,使得两栈中元素弹出顺序完全相反,那么queue和stack2的弹出顺序就完全相同了.

在这里插入图片描述

后面要是有元素offer进来,就固定放入stack1就好,等到stack2中元素弹出完了以后,再把stack1中的元素全部放进stack2.这样分工明确,stack1只管压入元素,stack2只管弹出栈顶元素.

class MyQueue {Stack<Integer> stack1;Stack<Integer> stack2;public MyQueue() {stack1=new Stack<>();stack2=new Stack<>();}public void push(int x) {stack1.push(x);}public int pop() {if(empty()) return -1;if(stack2.empty()) {while(!stack1.empty()) {stack2.push(stack1.pop());}}return stack2.pop();}public int peek() {if(empty()) return -1;if(stack2.empty()) {while(!stack1.empty()) {stack2.push(stack1.pop());}}return stack2.peek();}public boolean empty() {return stack1.isEmpty() && stack2.isEmpty();}
}

文章转载自:

http://w4Gv0Jyq.pqcsx.cn
http://UP0ScS49.pqcsx.cn
http://X22VWSvz.pqcsx.cn
http://iiovDLQe.pqcsx.cn
http://arFv2KrB.pqcsx.cn
http://6ltZFspL.pqcsx.cn
http://CgvkcylY.pqcsx.cn
http://a3XdwUqr.pqcsx.cn
http://QMod7FrI.pqcsx.cn
http://4EzVER9s.pqcsx.cn
http://1Rv959UU.pqcsx.cn
http://5Hjula68.pqcsx.cn
http://vJjJ28G4.pqcsx.cn
http://yKmghzwa.pqcsx.cn
http://vlpmoGf6.pqcsx.cn
http://9CEWg6T6.pqcsx.cn
http://sFEM3U6r.pqcsx.cn
http://mIAUXbgy.pqcsx.cn
http://GsYMB8uo.pqcsx.cn
http://AldCGO4R.pqcsx.cn
http://4lvHR6Mn.pqcsx.cn
http://mifq6sx2.pqcsx.cn
http://8b6zp9YD.pqcsx.cn
http://3Dig4sSM.pqcsx.cn
http://viBSthLm.pqcsx.cn
http://kt2BADuQ.pqcsx.cn
http://QzAb2OKW.pqcsx.cn
http://Aajjihxr.pqcsx.cn
http://rT0OAaAV.pqcsx.cn
http://lPvPnKZs.pqcsx.cn
http://www.dtcms.com/a/383497.html

相关文章:

  • Cherno OpenGL 教程
  • RT-DETRv2 中的坐标回归机制深度解析:为什么用 `sigmoid(inv_sigmoid(ref) + delta)` 而不是除以图像尺寸?
  • OpenCV入门教程
  • 深度学习-计算机视觉-目标检测三大算法-R-CNN、SSD、YOLO
  • 冰火两重天:AI重构下的IT就业图景
  • 从ENIAC到Linux:计算机技术与商业模式的协同演进——云原生重塑闭源主机,eBPF+WebAssembly 双引擎的“Linux 内核即服务”实践
  • 从 MySQL 迁移到 GoldenDB,上来就踩了一个坑。
  • qt界面开发入门以及计算器制作
  • SQL 核心概念与实践总结
  • 【Tourbox】怎么复制预设?
  • RTT操作系统(2)
  • 基于STM32单片机智能手表GSM短信上报GPS定位防丢器设计
  • 力扣658.找到K个最接近的元素
  • LeetCode 面试经典 150_哈希表_赎金信(39_383_C++_简单)
  • LeetCode热题100--114. 二叉树展开为链表--中等
  • 【交易系统系列33】从Raft到Kafka:解构交易所核心系统的一致性与数据持久化之道
  • 数据结构---基于顺序存储结构实现的双端队列
  • C4D建模入门指南:核心术语与高效设置详解
  • Unity核心概念⑧:Input
  • 软考高级-系统架构设计师之指令系统
  • Kafka 运维实战基本操作含命令与最佳实践
  • CAS理解
  • Linux动静态库开发基础:静态库与动态库的编译构建、链接使用及问题排查
  • 深度学习的定义
  • 数据库造神计划第七天---增删改查(CRUD)(3)
  • 【WitSystem】FastAPI目录架构最佳实践
  • Python的re模块
  • 条件扩散过程(附录H)
  • selenium web自动化测试
  • docker compose 部署dify