• 数据结构:队列


    特点

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

    手搓一个队列 

     

    链式队列

     

    开始动手

    队列属于插入元素后需要从头部来删除,我们可以用双链表来模拟它

    尾巴进,头部出 

    接下来整个的过程跟双链表差不多,大家可以参考我之前一篇博客

    链表(3):双链表_cx努力编程中的博客-CSDN博客

    初始化 

    1. static class ListNode{
    2. public int val;
    3. public ListNode next;
    4. public ListNode prev;
    5. public ListNode(int val){
    6. this.val = val;
    7. }
    8. }
    9. public ListNode head;
    10. public ListNode last;
    11. public int usedSize;

    offer

    尾插法

    1. public boolean offer(int val){
    2. ListNode node = new ListNode(val);
    3. if(head == null){
    4. head = node;
    5. last = node;
    6. }else{
    7. last.next = node;
    8. node.prev = last;
    9. last = last.next;
    10. }
    11. usedSize++;
    12. return true;
    13. }

    poll

    1. public int poll(){
    2. //没有节点
    3. if(head == null){
    4. return -1;
    5. }
    6. int retVal = head.val;
    7. //只有一个节点
    8. if(head.next == null){
    9. head = null;
    10. last = null;
    11. return retVal;
    12. }
    13. //两个及以上的节点
    14. head = head.next;
    15. head.prev = null;
    16. usedSize--;
    17. return retVal;
    18. }

    peek

    1. public int peek(){
    2. if(head == null){
    3. return -1;
    4. }
    5. return head.val;
    6. }

    empty和size

    1. public boolean empty(){
    2. return head == null;
    3. }
    4. public int size(){
    5. return usedSize;
    6. }

    数组队列

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

    简单介绍一下

    假设有一个容量为5的数组,要操作12 23 34 45 56 67 78 7个数字

    队头front,队尾rear先放在0位置,分别往后遍历

    每次加入一个元素rear就++,每次弹出一个元素front就++

     

    但是这会出现一个问题

    当我们加到56这个元素之后,rear跑到数组外面去了,越界了

    极限一点,当我们把当前队列的元素全部poll之后,front也跑到数组外面去了

    其实队列弹出元素后,前面的必然会是空的,我们可以让rear走到前面来

    整个队列弹空了之后也把front移到前面的空格处

    如图,我们依次往队列加45 56 12 23 34,此时rear走到数组末端,我们把45弹出,rear就可以重新回到数组头

    这么一看,整个数组队列就是一个循环,一个圈

     

    🆗我们依次加入元素,每次加入rear就往后走

    rear走了一圈又和front相遇了,此时问题来了

    1. 队列此时是空的还是满的?

    (1)使用usedSize来记录,放入一个元素usedSize++

    (2)浪费一个空间来表示满

    相当于你要过河,总得扔个石头试试深浅的道理一样,在front前面开辟一块空间,什么元素都不放,当rear走到这块空间时,判断一下rear下一个元素位置是不是front,是的话就证明队列满了

    (3)使用标记

    第一次相遇(起始位置)标记一下,第二次相遇的时候就证明它满了

    2. rear怎么从7下标来到0下标?

    公式:rear = (rear + 1) % len

    front = (front + 1) % len

    1. public boolean isEmpty() {
    2. return front == rear;
    3. }
    4. public boolean isFull() {
    5. return (rear+1) % elem.length == front;
    6. }

    入队和出队

    1. public MyCircularQueue(int k) {
    2. elem = new int[k];
    3. }
    4. //入队
    5. public boolean enQueue(int value) {
    6. if(isFull()){
    7. return false;
    8. }
    9. elem[rear] = value;
    10. rear = (rear+1) % elem.length;
    11. return true;
    12. }
    13. //出队
    14. public boolean deQueue() {
    15. if(isEmpty()){
    16. return false;
    17. }
    18. front = (front + 1) % elem.length;
    19. return true;
    20. }

    队头和队尾元素

    1. //得到队头元素
    2. public int Front() {
    3. if(isEmpty()){
    4. return -1;
    5. }
    6. return elem[front];
    7. }
    8. //得到队尾元素
    9. public int Rear() {
    10. if(isEmpty()){
    11. return -1;
    12. }
    13. int index = (rear == 0) ? elem.length - 1:rear-1;
    14. return elem[index];
    15. }

    得到队尾元素的部分要注意一下,不能直接rear-1,因为如果rear=0的时候,rear-1=-1是不合法的

    🆗你以为结束了吗?当我们把这段代码放入到力扣里面,我们发现报错了,报错结果:

    在执行3的入队操作时,预期的是true,而我们输出了false

    当我们空间为3的时候,确实只能存2个元素,因为存第3个元素空间会被浪费

    那我们可以投机地改一下代码

    或者使用usedSize,就没有浪费空间这么一说了

    代码具体就是定义完usedSize,enQueue就usedSize++,deQueue就usedSize--


    双端队列

    指的是在队列两边都可以进行入队和出队的操作

    链式队列就能实现这个功能

     

    那数组队列也可以实现吗

    ArrayDeque的底层也是有这些头插尾插方法的

     

    这两个不仅仅可以当作队列,也可以当作栈。这两个当作栈的情况比较多


    队列题目

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

    请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

    实现 MyStack 类:

    • void push(int x) 将元素 x 压入栈顶。
    • int pop() 移除并返回栈顶元素。
    • int top() 返回栈顶元素。
    • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

    我们知道,队列和栈的出栈顺序本来就是矛盾的,所以一个队列实现不了栈,但是两个队列可以啊

    假设我们要操作12 23 34 45这四个数,把前3个数加入后,栈要弹出元素,就是34第一个出

     

    观察到qu2队列是空的,那qu1就把12和23分别弹出来扔到qu2里面存储起来,qu1再把34弹出,这样就实现了栈弹出34的操作

    解题步骤:

    1.入栈:哪个队列不为空就放到哪个队列里面,两个都为空就扔到qu1里面

    2.出栈:哪个队列不为空就出size-1个元素,并扔到空队列里面

    3.当两个队列都为空的时候,栈就是空的

    整个代码

    1. class MyStack {
    2. private Queue qu1;
    3. private Queue qu2;
    4. public MyStack() {
    5. qu1 = new LinkedList<>();
    6. qu2 = new LinkedList<>();
    7. }
    8. public void push(int x) {
    9. if(!qu1.isEmpty()){
    10. qu1.offer(x);
    11. }else if(!qu2.isEmpty()){
    12. qu2.offer(x);
    13. }else{
    14. //两个队列都是空的,指定放到qu1里面
    15. qu1.offer(x);
    16. }
    17. }
    18. public int pop() {
    19. if(empty()){
    20. return -1;
    21. }
    22. if(!qu1.isEmpty()){
    23. int size1 = qu1.size();//让一个size1记录qu1的大小,防止循环的时候循环条件里的size没有变化
    24. for (int i = 0; i < size1-1; i++) {
    25. int x = qu1.poll();
    26. qu2.offer(x);
    27. }
    28. return qu1.poll();
    29. }else{
    30. int size2 = qu2.size();
    31. for (int i = 0; i < size2-1; i++) {
    32. int x = qu2.poll();
    33. qu1.offer(x);
    34. }
    35. return qu2.poll();
    36. }
    37. }
    38. public int top() {
    39. if(empty()){
    40. return -1;
    41. }
    42. if(!qu1.isEmpty()){
    43. int size1 = qu1.size();
    44. int x = -1;
    45. for (int i = 0; i < size1; i++) {
    46. x = qu1.poll();
    47. qu2.offer(x);
    48. }
    49. return x;
    50. }else{
    51. int x = -1;
    52. int size2 = qu2.size();
    53. for (int i = 0; i < size2; i++) {
    54. x = qu2.poll();
    55. qu1.offer(x);
    56. }
    57. return x;
    58. }
    59. }
    60. public boolean empty() {
    61. return qu1.isEmpty() && qu2.isEmpty();
    62. }
    63. }


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

    请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

    实现 MyQueue 类:

    • void push(int x) 将元素 x 推到队列的末尾
    • int pop() 从队列的开头移除并返回元素
    • int peek() 返回队列开头的元素
    • boolean empty() 如果队列为空,返回 true ;否则,返回 false

    和上面那道题同理,一个栈实现不了队列,所以我们要用两个栈

    1.入队的时候,放到s1里面

    2.出队的时候,都出s2当中的元素,当s2没有元素的时候,把s1里面的元素全部倒过来

    整个的代码:

    1. class MyQueue {
    2. private Stack s1;
    3. private Stack s2;
    4. public MyQueue() {
    5. s1 = new Stack<>();
    6. s2 = new Stack<>();
    7. }
    8. public void push(int x) {
    9. s1.push(x);
    10. }
    11. public int pop() {
    12. if(empty()){
    13. return -1;
    14. }
    15. if(s2.empty()){
    16. while(!s1.empty()){
    17. s2.push(s1.pop());
    18. }
    19. }
    20. return s2.pop();
    21. }
    22. public int peek() {
    23. if(empty()){
    24. return -1;
    25. }
    26. if(s2.empty()){
    27. while(!s1.empty()){
    28. s2.push(s1.pop());
    29. }
    30. }
    31. return s2.peek();
    32. }
    33. public boolean empty() {
    34. return s1.empty() && s2.empty();
    35. }
    36. }

  • 相关阅读:
    Ubuntu-24.04-live-server-amd64安装界面中文版
    关于2023年编程语言使用排行我的看法
    公开IP属地信息如何保护用户的隐私?
    【NOWCODER】- Python:循环语句(二)
    allegro画完封装怎么老报错保存不了呢
    spire.pdf盖章(无水印免费无限制)
    Flink SQL: LOAD Statements
    Java项目:SSM网上家具商城网站系统平台
    LeetCode693. 交替位二进制数
    K8S 极速入门
  • 原文地址:https://blog.csdn.net/hellg/article/details/133848728