今天我们来学习队列🧐
队列是由双向链表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创建的差别
我们来看下面两行代码👇:
Queue<Integer> qu1 = new LinkedList<>();
LinkedList<Integer> 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这个成员属性的。
下面开始真正的仿写😎
public class MyLinkedList {
public static class Node{
public int val;
public Node next;
public Node(){
}
public Node(int val){
this.val = val;
}
}
public Node head;
public Node last;
//从尾入,从头出
//尾插法
public void offer(int val){
if(head==null){
head = new Node(val);
last = head;
}
Node cur = new Node(val);
last.next = cur;
last = cur;
}
public int poll(){
if(head==null||last==null){
throw new QueueEmptyException("队列为空");
}
int val = head.val;
head = head.next;
return val;
}
public int peek(){
if(head==null||last==null){
throw new QueueEmptyException("队列为空");
}
int val = head.val;
return val;
}
public int size(){
Node cur = head;
int size = 0;
while(cur!=null){
cur = cur.next;
size++;
}
return size;
}
}
以上是用链表实现,再来看用数组要怎样实现仿写?
✨数组作为底层仿写
数组仿写相对来说较为麻烦,因为数组仿写会有空间浪费的情况出现,原因是当我们出队列的时候front(队头)下标会往后走,从而产生空间浪费。
这是,要让我们的空间不会浪费,就需要用到循环数组👇:
当我们入队列或者出队列的时候,front下标和rear下标都可以跟着,以达到不浪费空间的情况。
在利用循环数组创建列表前,要先理清两个问题:
- 如何判断数组什么时候空或是满
- 当rear或者front下标到达图中的7下标时,要怎么样到下标0
1️⃣首先解决第一个问题,要判断空或是满,有三种解决方法:①使用flag;②实际大小==数组大小;③将front位置的前一个位置置为空,不放入数据。在后面的代码里我们用第三种方法解决。
2️⃣第二个问题要解决,我们可以使用(rear+1)%数组大小 这条公式就可以解决。
利用循环数组来仿写队列在LeetCode中是有题目的,我们可以通过尝试写这道题目来理解😎下面分别是LeetCode题目链接和博主写的该题目的链接👇
LeetCode第622题.设计循环队列(原题目) | LeetCode第622题—设计循环队列(博主讲解版本) |
在模仿完了队列后,相信我们会对队列的了解更加深刻😎
🍃实际写题应用
在学习完队列后,我们可以开始写题了,下面是博主认为不错的题目,分享给大家,也有博主的题目分析😎
LeetCode原题 | 博主分析 |
以上!便是全部的啦😎
又是收获满满的一天~