Queue——队列

在这里插入图片描述

⭐️前言⭐️

本篇文章介绍与栈比较相像的另一种线性数据结构——队列(Queue),它与栈的数据操作不同。

🍉博客主页: 🍁【如风暖阳】🍁
🍉精品Java专栏【JavaSE】【备战蓝桥】、【JavaEE初阶】【MySQL】【数据结构】
🍉欢迎点赞 👍 收藏留言评论 📝私信必回哟😁

🍉本文由 【如风暖阳】 原创,首发于 CSDN🙉

🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

🍉博客中涉及源码及博主日常练习代码均已上传码云(gitee)GitHub


Queue——队列

🍅1.概念

只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性。

入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
在这里插入图片描述

🍅2.队列的使用

在这里插入图片描述
注意:在Java中,Queue是个接口,所以在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口

方法功能
boolean offer(E e)入队列
E poll()出队列
peek()获取队头元素
int size()获取队列中有效元素个数
boolean isEmpty()检测队列是否为空

代码实例

  public static void main(String[] args) {
        Queue<Integer> queue=new LinkedList<>();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek());//1
        System.out.println(queue.poll());//1
        System.out.println(queue.peek());//2
}

🍅3.队列模拟实现

队列中既然可以存储元素,那底层必须要有能够保存元素的空间,通过前边线性表的的学习我们了解到常见的空间类型有两种:顺序结构和链式结构,而我们队列的实现,如果使用顺序结构,插入数据的时间复杂度为O(1),删除数据的时间复杂度为O(N)(删除头数据需要数组后边的数据整体前移),说明并不能很高效的完成删除操作,所以我们用链式结构来存储。但当我们如果考虑用链式结构来存储时,有两种情况:

  • 头插尾删:头插入队列的时间复杂度为O(1),尾删出队列的时间复杂度为O(N).
  • 尾插头删:尾插入队列的时间复杂度为O(N),头删出队列的时间复杂度为O(1).

由上边的两种情况我们也可以看出,即使是链式存储也并不能高效的完成任务,但如果我们加一个last引用指向尾节点,再以尾插入队列,头删出队列的方式其时间复杂度将都会成为O(1).
在这里插入图片描述

public class MyQueue {
    static class Node {
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;//队列的头
    public Node tail;//队列的尾

    /**
     * 入队操作
     * @param val
     */
    public void offer(int val) {
        Node node=new Node(val);
        if(head==null) {
            head=node;
            tail=node;
        }else {
            tail.next=node;
            tail=tail.next;
        }
    }
    /**
     * 出队操作
     */
    public int poll() {
        if(head==null)
            throw new RuntimeException("队列为空!");
        int val= head.val;;
        if(head.next==null)
            head=tail=null;
        else
            head=head.next;
        return val;
    }
    /**
     * 查看队头元素
     */
    public int peek() {
        if(head==null) {
            throw new RuntimeException("队列为空!");
        }
        return head.val;
    }
}

🍅4.循环队列

循环队列通常使用数组实现。
在这里插入图片描述
如上图所示,front代表队列头,rear代表队列尾的前一个位置(用于判断当前位置是否可以成为新队尾),当front==rear时队列为空。
为了实现数组下标的循环,在后移的时候使用取余的方式,例如rear需要后移一步,则rear=(rear+1)%len,len为数组的长度。
如果队列满了,就成为下图的情况:
在这里插入图片描述
此时front又和rear相等了,我们判断队列满有两种方式,一种是使用usedSize属性记录添入元素个数,当usedSize == length时队列满。另一种方式是浪费最后一个格子,如下图,当(rear+1)%len == front时,就判断为满不再添加元素了。
在这里插入图片描述
我们来做一道力扣练习题来更好的了解循环队列。
【622. 设计循环队列】
题意:
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

示例:

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4

代码:
方法一:利用usedSize记录有效数据个数

class MyCircularQueue {
    int []array;//存储数组
    int front;//队列头
    int rear;//队列尾的前一个位置
    int usedSize;//记录有效数据个数
    public MyCircularQueue(int k) {
        array=new int[k];
        front=0;
        rear=0;
        usedSize=0;
    }
    
    public boolean enQueue(int value) {
        if(isFull())
        return false;
        else {
            array[rear]=value;
            usedSize++;
            rear=(rear+1)%array.length;
            return true;
        }
    }
    
    public boolean deQueue() {
        if(isEmpty())
        return false;
        else {
        //删除队列头直接让头后移一个位置并让usedSize--即可,当在有新的数据插入到此位置时会把原来的数据覆盖
            front=(front+1)%array.length;
            usedSize--;
            return true;
        }
    }
    
    public int Front() {
        if(isEmpty())
        return -1;
        else {
            return array[front];
        }
    }
    
    public int Rear() {
        if(isEmpty())
        return -1;
        else {
        //特殊情况:0下边的前一个位置时数组的最后一个位置
            int index=rear-1;
            if(rear==0)
            index=array.length-1;
            return array[index];
        }
    }
    
    public boolean isEmpty() {
        return usedSize==0;
    }
    
    public boolean isFull() {
        return usedSize==array.length;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

方法二:浪费一个位置来判满

class MyCircularQueue {
    int []array;
    int front;
    int rear;
    public MyCircularQueue(int k) {
        array=new int[k+1];
        front=0;
        rear=0;
    }
    
    public boolean enQueue(int value) {
        if(isFull())
        return false;
        else {
            array[rear]=value;
            rear=(rear+1)%array.length;
            return true;
        }
    }
    
    public boolean deQueue() {
        if(isEmpty())
        return false;
        else {
            front=(front+1)%array.length;
            return true;
        }
    }
    
    public int Front() {
        if(isEmpty())
        return -1;
        else {
            return array[front];
        }
    }
    
    public int Rear() {
        if(isEmpty())
        return -1;
        else {
            int index=rear-1;
            if(rear==0)
            index=array.length-1;
            return array[index];
        }
    }
    
    public boolean isEmpty() {
        return rear==front;
    }
    
    public boolean isFull() {
        return (rear+1)%array.length==front;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

🍅5.双端队列(Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
在这里插入图片描述
在这里插入图片描述
Deque是一个接口,使用时必须创建LinkedList的对象。
使用:
在这里插入图片描述
双端队列既能作为栈来使用,也能作为队列来使用。
当作栈时调用push方法,将会从底层链表头插数据,pop方法从底层链表头删数据。
当作队列时调用offer方法,将会从底层链表尾插数据,poll方法从链表头删数据。
两者都适用于peek方法,因为在源码中,peek方法返回链表头,就是栈的栈顶,也就是队列的队头。

🍅5.经典面试题

1.【232. 用栈实现队列】
题意:
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

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

你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

思路:

在这里插入图片描述
入队列时入进栈,出队列时把数据从入栈出,进出栈内,再从出栈出数据,就模拟实现了队列的先进先出。

代码:

class MyQueue {
    Stack<Integer> stackIn;
    Stack<Integer> stackOut;
    public MyQueue() {
        stackIn=new Stack<>();
        stackOut=new Stack<>();
    }

    public void push(int x) {
        stackIn.push(x);
    }

    public int pop() {
        fun();
        return stackOut.pop();
    }

    public int peek() {
        fun();
        return stackOut.peek();
    }

    public boolean empty() {
        return stackIn.isEmpty()&&stackOut.isEmpty();
    }
    private void fun() {
    //当出栈不为空时,直接返回,此时可以直接从出栈出数据
    //当出栈为空时,需要把入栈内的数据全部出到出栈中。
        if(!stackOut.isEmpty()) return;
        while (!stackIn.isEmpty()) {
            stackOut.push(stackIn.pop());
        }
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

2.【225. 用队列实现栈】
题意:
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
注意:

你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

思路:

用队列来模拟栈的先进后出,此时就需要通过两个栈来更改数据的顺序,来模拟实现先进后出。
请添加图片描述
在每次新入元素时,让该元素先进入queue2中,如果queue1中元素不为空,就让queue1中元素依次出队列进入queue2中,再交换queue1和queue2,这样就可以通过queue2调整元素的顺序,使得queue1中的元素顺序模拟了栈的先入后出。

代码:

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;
    public MyStack() {
        queue1=new LinkedList<>();
        queue2=new LinkedList<>();
    }

    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        //交换queue1和queue2,交换完成后queue1成为模拟栈,queue2又变为空
        Queue<Integer> temp=queue1;
        queue1=queue2;
        queue2=temp;
    }

    public int pop() {
        return queue1.poll();
    }

    public int top() {
        return queue1.peek();
    }

    public boolean empty() {
        return queue1.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

3.【155. 最小栈】
题意:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

思路:
利用辅助栈来存储栈中最小的元素,入栈时数据栈正常入栈,辅助栈在为空时或者数据小于等于栈顶元素时,辅助栈也入栈数据。此时如果获取堆栈中最小元素,直接返回辅助栈的栈顶元素即可。在删除栈顶元素时,需要判断数据栈要删除的元素是否和辅助栈的栈顶元素相同,相同的情况下辅助栈也需要删除元素。

代码:

class MinStack {
    Stack<Integer> stack;
    Stack<Integer> minStack;
    public MinStack() {
        stack=new Stack<>();
        minStack=new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(minStack.empty()||val<=minStack.peek())
        minStack.push(val);
    }
    
    public void pop() {
        int val=stack.pop();
        if(minStack.peek()==val)
        minStack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

相似题目,点击直达【数据结构专栏刷题】


⚡️最后的话⚡️

总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁
在这里插入图片描述

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如风暖阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值