• Java数据结构—队列


    今天我们来学习队列🧐

     

    队列是由双向链表LinkedList作为底层的一个接口,为数据结构的一种。下面让我们正式开始队队列的学习吧😎

    🌱队列的特点

    队列的特点是先进先出,就像排队一样,队尾进队头出


    🌱队列

    🌿Queue的方法

    Queue的方法不多,只有以下六种👇:

    方法名作用
    add(E)在队尾添加元素
    offer(E)在队尾添加元素
    remove()弹出队头元素
    poll()弹出队头元素
    element()返回队头元素但不弹出
    peek()返回队头元素但不弹出

    细心的同学可能会发现,怎么Queue的这六个方法两个两个都是重复的🧐它们又有什么不同嘞?

    ✨add和offer方法的区别

    其实在平常的使用中,我们常使用的方法有三个:offer(E)、poll()、peek()。那么它们的真正区别是什么呢?

    要知道这些相同作用的方法有什么区别,还要从源头——源码查起

    在IDEA上,我们通过使用ctrl+鼠标左键点击进入到add和offer的源码,在add和offer的上方我们分别可以看到其注释👇

    将add和offer的注释分别翻译后比较👇:

     我们可以由上面的图得知,在容量满的时候,offer相较于add的直接抛异常,offer会好一些不会直接暂停程序。

    当然,其实选择哪一个方法其实差别都不算太大,作者较喜欢使用的是offer、poll、peek这三个函数😎大家可以自己选择


    🌿Queue创建与LinkedList创建的差别

    我们来看下面两行代码👇:

    1. Queue qu1 = new LinkedList<>();
    2. LinkedList qu2 = new LinkedList<>();

    这两行代码虽然后半段都是相同的,但由于创建变量的类型不同,导致qu1的方法会远远少于qu2,这是因为qu1的类型是接口,而Queue接口只有6个方法,要创建实体得利用LinkedList,但是只是相当于LinkedList将Queue中的方法给重写了,而Queue实际只有6个方法的这个事实无法改变。通过在IDEA中试一下就可知道👇:

     在解决完这个问题后,我们又想,为什么LinkedList会有这么多方法?🧐

    ✨LinkedList不仅继承了Queue接口,并且继承了Deque接口

    Deque接口的底层是一个双端队列,也就是怎么进出都可。如:队头进,队头出;队头进,队尾出;队尾进,队头出;队尾进,队尾出;


    🌿Queue的仿写

    要正式了解一个数据结构,仿写是一个最好的了解方式,下面我们可以自己仿写一个Queue。

    ✨链表作为底层仿写

    若在单向链表的情况下,我们需要考虑一个问题,入队的时候是使用头插还是尾插?

    根据头插和尾插入队来分析:

    • 若使用头插,则入队的时间复杂度为O(1),但出队的时间复杂度要O(n)。
    • 若使用尾插,则入队的时间复杂度要O(n),但出队的时间复杂度为O(1)。

    所以头插尾插入队都差不多,要是想要入队出队时间复杂度都为O(1)的话,我们需要设置多一个last节点,专门在最后一个节点的位置处,用于出队,这样就可以让入队出队的时间复杂度都为O(1)了。但我们要知道,真正的单向链表中,是没有last这个成员属性的。

    下面开始真正的仿写😎

    1. public class MyLinkedList {
    2. public static class Node{
    3. public int val;
    4. public Node next;
    5. public Node(){
    6. }
    7. public Node(int val){
    8. this.val = val;
    9. }
    10. }
    11. public Node head;
    12. public Node last;
    13. //从尾入,从头出
    14. //尾插法
    15. public void offer(int val){
    16. if(head==null){
    17. head = new Node(val);
    18. last = head;
    19. }
    20. Node cur = new Node(val);
    21. last.next = cur;
    22. last = cur;
    23. }
    24. public int poll(){
    25. if(head==null||last==null){
    26. throw new QueueEmptyException("队列为空");
    27. }
    28. int val = head.val;
    29. head = head.next;
    30. return val;
    31. }
    32. public int peek(){
    33. if(head==null||last==null){
    34. throw new QueueEmptyException("队列为空");
    35. }
    36. int val = head.val;
    37. return val;
    38. }
    39. public int size(){
    40. Node cur = head;
    41. int size = 0;
    42. while(cur!=null){
    43. cur = cur.next;
    44. size++;
    45. }
    46. return size;
    47. }
    48. }

    以上是用链表实现,再来看用数组要怎样实现仿写?


    ✨数组作为底层仿写

    数组仿写相对来说较为麻烦,因为数组仿写会有空间浪费的情况出现,原因是当我们出队列的时候front(队头)下标会往后走,从而产生空间浪费。

     这是,要让我们的空间不会浪费,就需要用到循环数组👇:

     当我们入队列或者出队列的时候,front下标和rear下标都可以跟着,以达到不浪费空间的情况。

    在利用循环数组创建列表前,要先理清两个问题:

    1. 如何判断数组什么时候空或是满
    2. 当rear或者front下标到达图中的7下标时,要怎么样到下标0

    1️⃣首先解决第一个问题,要判断空或是满,有三种解决方法:①使用flag;②实际大小==数组大小;③将front位置的前一个位置置为空,不放入数据。在后面的代码里我们用第三种方法解决。

    2️⃣第二个问题要解决,我们可以使用(rear+1)%数组大小   这条公式就可以解决。

    利用循环数组来仿写队列在LeetCode中是有题目的,我们可以通过尝试写这道题目来理解😎下面分别是LeetCode题目链接和博主写的该题目的链接👇

    LeetCode第622题.设计循环队列(原题目)LeetCode第622题—设计循环队列(博主讲解版本)

    在模仿完了队列后,相信我们会对队列的了解更加深刻😎


    🍃实际写题应用

    在学习完队列后,我们可以开始写题了,下面是博主认为不错的题目,分享给大家,也有博主的题目分析😎


    以上!便是全部的啦😎

    又是收获满满的一天~

  • 相关阅读:
    C++学习笔记(二十二)
    HDU3595 GG and MM
    车载氧吧E-mark认证流程是怎样的?
    B031-网络编程 Socket Http TomCat
    聊聊日报设计——日报怎么写,日报有何用?
    Jenkins教程-20-常用插件-Parameterized Trigger
    PyQt5可视化编程-菜单和工具栏
    Java重写与重载
    Python Web Flask—SQLAlchemy
    数据管理生态的核心解析:数据库、数据仓库、数据湖、数据平台与数据中台的关系与实现
  • 原文地址:https://blog.csdn.net/Green_756/article/details/126539895