【详解栈Stack与队列Queue】

news2024/11/19 13:26:50

🌠作者:@TheMythWS.

🎆专栏:《集合与数据结构》

🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。 

目录

栈 ( Stack )  

栈的概念

栈的使用

栈的模拟实现 

栈的应用场景 

1. 改变元素的序列

2. 将递归转化为循环 

3. 括号匹配 

4. 逆波兰表达式求值  

5. 出栈入栈次序匹配 

6. 最小栈 

概念区分

队列(Queue) 

概念

队列的使用 

队列模拟实现 

顺序队列 

循环队列 

练习题

双端队列 (Deque) 

面试题

1. 用队列实现栈

2. 用栈实现队列


栈 ( Stack )  

栈的概念

定义:限定只在表的一端(表尾)进行插入和删除操作的线性表
特点:后进先出(LIFO) 或者 先进后出(FILO)
允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)  
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,  出数据在栈顶。  

形象理解:

栈的使用

因为栈相对比较简单, 我们先使用栈再模拟实现.

 public static void main(String[] args) {
        Stack<Integer> s = new Stack();
        s.push(1);
        s.push(2);
        s.push(3);
        s.push(4);
        System.out.println(s.size()); // 获取栈中有效元素个数---> 4
        System.out.println(s.peek()); // 获取栈顶元素---> 4
        s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
        System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
        if (s.empty()) {
            System.out.println("栈空");
        } else {
            System.out.println(s.size());
        }
    }
    public static void main1(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        Integer a = stack.pop();//3
        System.out.println(a);
        Integer b = stack.peek();//2
        System.out.println(b);
        Integer b2 = stack.peek();//2
        System.out.println(b2);
        System.out.println(stack.size());//2
        System.out.println(stack.isEmpty());
    }

栈的模拟实现 

从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的,效率低(淘汰)。

public class MyStack {
    public int[] elem;
    public int usedSize;
    public MyStack() {
        this.elem = new int[10];
    }
    //压栈
    public void push(int val) {
        if (isFull()) {
            //满了就扩容
            elem = Arrays.copyOf(elem, 2 * elem.length);
        }
        /*elem[usedSize] = val;
        usedSize++;*/
        elem[usedSize++] = val;
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    //出栈
    public int pop() {
        if (isEmpty()) {
            throw new EmptyException("栈空");
        }
        /*int val = elem[usedSize - 1];//要出去的值
        usedSize--;
        return val;*/
        /*usedSize--;
        return elem[usedSize];*/
        return elem[--usedSize];
    }
    public boolean isEmpty() {
        return usedSize == 0;
    }
    //获取栈顶元素
    public int peek() {
        if (isEmpty()) {
            throw new EmptyException("栈为空, 无法获取栈顶元素.");
        }
        return elem[usedSize - 1];
    }
    public int size() {
        return usedSize;
    }
}
public class EmptyException extends RuntimeException{
    public EmptyException() {
    }
    public EmptyException(String message) {
        super(message);
    }
}

栈的应用场景 

1. 改变元素的序列

(1) 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1

(2)一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA  

2. 将递归转化为循环 

比如:逆序打印链表   

/*
    逆序打印链表(递归的方式)
     */
    public void reverseDisplay(ListNode pHead) {
        //写法1:
        if (pHead == null) {//没有结点
            return;
        }
        if (pHead.next == null) {//只有一个结点
            System.out.print(pHead.val + " ");
            return;
        }
        reverseDisplay(pHead.next);
        System.out.print(pHead.val + " ");
        //写法2:
        /*if (pHead != null) {//出口:pHead.next == null
            reverseDisplay(pHead.next);
            System.out.print(pHead.val + " ");
        }*/
    }
    /*
    逆序打印链表(遍历栈的方式)
     */
    public void displayWithStack(ListNode head) {
        if (head == null) {
            return;
        }
        Stack<ListNode> s = new Stack<>();
        //将链表中的结点保存在栈中
        ListNode cur = head;
        while (cur != null) {
            s.push(cur);
            cur = cur.next;
        }
        //遍历栈, 将栈中的元素出栈
        while (!s.empty()) {
            System.out.print(s.pop().val + " ");
        }
    }
public static void main(String[] args) {
        MySingleLinkedList list = new MySingleLinkedList();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        //逆序打印链表(递归)
        list.reverseDisplay(list.head);
        System.out.println("");
        //逆序打印链表(遍历栈)
        list.displayWithStack(list.head);
    }

如有需要,MySingleLinekdList的代码参看前面的LInkedList章节。

3. 括号匹配 

做题链接:力扣

public boolean isValid(String s) {
        /*
        ([)]
        ())
        (()
        )()
         */
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);//ch ( ) { } [ ]
            if (ch == '(' || ch == '{' || ch == '[') {//左括号就入栈
                stack.push(ch);
                //stack里面的一定是左括号
            } else {
                //遇到右括号
                if (stack.empty()) {
                    //一开始就遇到右括号, 且栈是空的.
                    return false;//说明此时右括号多.
                }
                char ch2 = stack.peek();//查看栈顶元素, 判断是否跟遍历的右括号匹配, 匹配就出栈.
                if (ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{' || ch == ']' && ch2 == '[') {
                    stack.pop();//匹配就出栈
                } else {
                    return false;//不匹配
                }
            }
        }
        if (!stack.empty()) {//栈不为空, 左括号多
            return false;
        }
        return true;
    }

4. 逆波兰表达式求值  

做题链接:力扣 

class Solution {
    public int evalRPN(String[] tokens) {
    Stack<Integer> stack = new Stack<>();
        for (String x : tokens) {
            if (!isOperation(x)) {//是数字就入栈
                stack.push(Integer.parseInt(x));
            } else {//是操作符就取出两个元素
                int num2 = stack.pop();//第二个操作数, 栈顶先弹出
                int num1 = stack.pop();//第一个操作数
                switch (x) {
                    case "+":
                        stack.push(num1 + num2);
                        break;
                    case "-":
                        stack.push(num1 - num2);
                        break;
                    case "*":
                        stack.push(num1 * num2);
                        break;
                    case "/":
                        stack.push(num1 / num2);
                        break;
                }
            }
        }
        return stack.pop();//peek()
    }
    private boolean isOperation(String x) {
        return (x.equals("+") || x.equals("-") || x.equals("/") || x.equals("*"));
    }
}

5. 出栈入栈次序匹配 

做题链接:力扣

class Solution {
    public boolean validateStackSequences(int[] pushA, int[] popA) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0; i < pushA.length; i++) {
            stack.push(pushA[i]);//先入栈, 再判断
            //注意j下标越界, 栈空指针异常的情况.
            //stack.peek() == popA[j] 修改为 stack.peek().equals(popA[j]), -128-127比较的是数值, 不在这个范围==比较的就是地址了, 所以保险起见, 用equals
            while (j < popA.length && !stack.empty() &&
                    stack.peek().equals(popA[j])) {//如果栈顶元素和j下标的值相等就出栈,同时j++
                stack.pop();
                j++;
            }
        }
        return stack.empty();
    }
}

6. 最小栈 

做题链接:力扣

class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack;
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    public void push(int val) {
        //同时往两个栈新增数据
        stack.push(val);
        if (minStack.empty()) {//minStack为空就入栈
            minStack.push(val);
        } else {
            //minStack不为空, 每次存入最小的值
            if (val <= minStack.peek()) {//注意<=的细节!!!遇到相同的最小的值, minStack也要入栈.
                minStack.push(val);
            }
        }
    }
    public void pop() {
        /*
        if (!stack.empty()) {
            Integer val = stack.pop();
            if (val.equals(minStack.peek())) {
                minStack.pop();
            }
        }
        */
        //如果stack出栈的元素 跟 最小栈的元素相等, 那么最小栈的元素也要出栈
        if (!stack.empty()) {//栈不为空, 才能出栈.
            //维护最小栈
            if (stack.pop().equals(minStack.peek())) {
                minStack.pop();
            }
        }
    }
    public int top() {//peek()
        if (!stack.empty()){
            return stack.peek();
        }
        return -1;
    }
    public int getMin() {
        return minStack.peek();
    }
}
/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

上面只介绍了栈的顺序存储,当然栈还有链式存储, 下面是链栈的使用

public static void main(String[] args) {
                                                                                //链栈
        LinkedList<Integer> linkedStack = new LinkedList<>();
        linkedStack.push(1);
        linkedStack.push(2);
        linkedStack.push(3);
        linkedStack.push(4);
        System.out.println(linkedStack.pop());
        System.out.println(linkedStack.peek());
    }

思考:为什么LinkedList可以被当作栈来使用?
主要是因为LinkedList具有栈的几个基本功能:

1.元素的插入和删除

LinkedList可以在列表的首部和尾部插入元素,也可以在指定位置删除元素。这与栈的特性相似,也是在栈顶插入元素和在栈顶删除元素。

2.后进先出(LIFO)

LinkedList也可以遵循后进先出(LIFO)的规则来访问和操作元素。通过在列表的头部插入元素和删除元素,就可以实现后进先出的功能。

3.可动态调整大小

LinkedList的大小是可动态调整的,可以动态添加或删除元素。同样,栈的大小也可以动态调整。

因此,由于LinkedList具有以上基本功能,可以像栈一样使用它。

概念区分

栈、虚拟机栈、栈帧有什么区别呢?   

  1. (Stack):是一种数据结构,具有“后进先出”的特点。在计算机程序中,栈常常被用来保存程序执行过程中的临时变量、函数调用的参数和返回值等。栈的存储空间是连续的,一般由操作系统分配和管理。

  2. 虚拟机栈(Java Virtual Machine Stack):虚拟机栈是Java虚拟机的一部分,用来存储Java方法的执行信息,包括方法的局部变量表、操作数栈、动态链接、方法返回地址和异常处理信息等。虚拟机栈的大小可以在启动JVM时指定,一般由JVM自动管理。

  3. 栈帧(Stack Frame):栈帧是程序执行时栈中存储的一块存储空间,用来保存一个方法的执行信息。栈帧一般包括局部变量表、操作数栈、动态链接、方法返回地址和异常处理信息等。每当一个方法被调用时,就会在栈中分配一个新的栈帧,该栈帧保存了调用方法的信息。当方法执行完毕时,该栈帧就被弹出栈。在Java虚拟机中,一个栈帧对应一个方法的执行。

总结起来,栈是一种数据结构,用来存储临时变量等信息;虚拟机栈是Java虚拟机的一部分,用来存储Java方法的执行信息;而栈帧则是程序执行时栈中存储的一块存储空间,用来保存一个方法的执行信息。

队列(Queue) 

概念

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

队列的使用 

在Java中,Queue是个接口,底层是通过链表实现的。  

public static void main(String[] args) {
        Queue<Integer> q = new LinkedList<>();
        q.offer(1);
        q.offer(2);
        q.offer(3);
        q.offer(4);
        q.offer(5); // 从队尾入队列
        System.out.println(q.size());//5
        System.out.println(q.peek()); // 获取队头元素1
        q.poll();//1出队列
        System.out.println(q.poll()); // 2从队头出队列,并将删除的元素返回
        if (q.isEmpty()) {
            System.out.println("队列空");
        } else {
            System.out.println(q.size());//剩余3个元素
        }
    }

思考:为什么LinkedList可以当作队列来使用?

LinkedList 可以当作队列来使用,是因为 LinkedList 实现了 Queue 接口,而 Queue 接口就是队列的抽象表示。因此,LinkedList 可以使用 Queue 接口中定义的方法,如 `add()`,`offer()`,`remove()`,`poll()`,`element()` 和 `peek()`等来操作元素,从而实现队列的功能。具体来说,LinkedList 的 `add()` 和 `offer()` 方法在队列尾部添加元素,`remove()` 和 `poll()` 方法移除队列头部的元素,`element()` 和 `peek()` 方法获取队列头部的元素,这些方法都符合队列的特点。 

注意Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。   

队列模拟实现 

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有
两种顺序结构 链式结构。 

思考下:队列的实现使用顺序结构还是链式结构好?   

对于队列的实现,顺序结构和链式结构都有各自的优缺点。

(1)顺序结构的优点是:实现简单,易于理解和实现,并且空间利用率高,不会出现指针浪费空间的情况。另外,顺序结构的访问速度比链式结构要快,因为可以直接通过下标来访问队列元素。

顺序结构的缺点是:在队列插入和删除元素时,需要移动大量的元素,效率较低,并且如果队列元素数量超过数组的长度,需要进行扩容操作,空间复杂度较高。

(2)链式结构的优点是:插入和删除元素时不需要移动其他元素,只需要改变指针的指向即可,效率较高,并且可以动态地分配存储空间,节约空间。

链式结构的缺点是:实现相对比较复杂,需要考虑指针的管理和指向的问题,并且由于指针本身需要占用额外的存储空间,因此空间利用率相对较低。

综上所述,选择顺序结构还是链式结构,需要根据具体的需求和实际情况来选择。如果需要高效地访问队列元素,并且队列元素数量不会太大,可以选择顺序结构。如果队列元素数量较大或者需要频繁插入和删除元素,可以选择链式结构

单链表实现

public class MyQueue {
    //单链表实现, 多了一个last尾巴结点的引用, 局限:只能尾部增加, 头部删除
    static class Node {
        public int val;
        public Node next;
        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;
    public Node last;//这儿引入一个尾结点的引用, 尾插法会很方便
    public int usedSize;
    //入队
    public void offer(int val) {
        Node node = new Node(val);
        if (head == null) {//第一次入队
            head = node;
            last = node;
        } else {//尾巴后面入队
            last.next = node;
            last = node;
        }
        usedSize++;
    }
    //出队
    public int poll() {
        if (empty()) {
            throw new EmptyException("队列为空");
        }
        int ret = head.val;
        head = head.next;
        if (head == null) {
            last = null;//只有一个结点, last也要置空
        }
        usedSize--;
        return ret;
    }
    public boolean empty() {
        return usedSize == 0;
    }
    //查看队首元素
    public int peek() {
        if (empty()) {
            throw new EmptyException("队列为空");
        }
        return head.val;
    }
}

双向链表实现

public class MyDoubleQueue {
    // 双向链表结点
    public static class ListNode {
        ListNode prev;
        ListNode next;
        int value;
        ListNode(int value) {
            this.value = value;
        }
    }
    ListNode head; // 队头
    ListNode last; // 队尾
    int size = 0;
    // 入队列---向双向链表位置插入新节点
    public void offer(int e) {
        ListNode newNode = new ListNode(e);
        if (head == null) {//第一次入队
            head = newNode;
            // last = newNode;
        } else {//尾巴后面入队
            last.next = newNode;
            newNode.prev = last;
            // last = newNode;
        }
        last = newNode;
        size++;
    }
    // 出队列---将双向链表第一个节点删除掉
    public int poll() {
        // 1. 队列为空
        // 2. 队列中只有一个元素----链表中只有一个节点---直接删除
        // 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
        int value = 0;
        if (empty()) {
            throw new EmptyException("队列为空");
        } else if (head == last) {
            last = null;
            head = null;
        } else {
            value = head.value;
            head = head.next;
            head.prev.next = null;
            head.prev = null;
        }
        size--;
        return value;
    }
    // 获取队头元素---获取链表中第一个节点的值域
    public int peek() {
        if (empty()) {
            throw new EmptyException("队列为空");
        }
        return head.value;
    }
    public int size() {
        return size;
    }
    public boolean empty() {
        return size == 0;
    }
}

顺序队列 

循环队列 

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。 

如何区分空与满:
1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记  

设计循环队列:力扣

采用保留一个位置的写法:

class MyCircularQueue {
    private int[] elem;
    private int front;//队首
    private int rear;//队尾

    public MyCircularQueue(int k) {
        //浪费一个空间的写法(保留一个位置的写法), 这儿必须要多 + 1
        this.elem = new int[k + 1];
        //this.elem = new int[k];
    }

    /**
     * 入队列
     *
     * @param value
     * @return
     */
    public boolean enQueue(int value) {
        //1.检查队列是否为满
        if (isFull()) {
            return false;
        }
        //2.队列不满
        elem[rear] = value;
        //rear++;//x
        rear = (rear + 1) % elem.length;
        return true;
    }

    /**
     * 出队列
     *
     * @return
     */
    public boolean deQueue() {
        //1.检查队列是否为空
        if (isEmpty()) {
            return false;
        }
        //2.队列不为空
        //front++;//x
        front = (front + 1) % elem.length;
        return true;
    }

    /**
     * 得到队首元素
     *
     * @return
     */
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }

    /**
     * 得到队尾元素
     *
     * @return
     */
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        /*
        return elem[rear - 1];
        这种写法有问题, 假设rear在0下标? 0 - 1 = -1 下标有问题.
        应该定义一个变量来判断rear在不同位置的下标变化.
        如果rear下标在0的位置, 说明数组的最后一个元素就是队尾元素
        否则就是rear - 1下标的元素
         */
        int index = (rear == 0) ? elem.length - 1 : rear - 1;
        return elem[index];
    }

    public boolean isEmpty() {
        return front == rear;
    }

    /**
     * 队列是否为满
     *
     * @return
     */
    public boolean isFull() {
        /*if ((rear + 1) % elem.length == front) {
            return true;
        }
        return false;*/
        return (rear + 1) % elem.length == front;
    }
}

练习题

1.数组Q[n]用来表示一个循环队列,f为当前队列头元素的前一位置,r为队尾元素的位置,假定队列中元素的个数小于n,计算队列中元素个数的公式为( )。
A. r-f
B. (n+f-r)%n
C. n+r-f
D.(n+r-f)%n
答案:D
解释:对于非循环队列,尾指针和头指针的差值便是队列的长度,而对于循环队列,差值可能为负数,所以需要将差值加上MAXSIZE(本题为n),然后与MAXSIZE(本题为n)求余,即(n + r - f) % n。
2.为解决计算机主机与打印机间速度不匹配问题,通常设一个打印数据缓冲区。主机将要输出的数据依次写入该缓冲区,而打印机则依次从该缓冲区中取出数据。该缓冲区的逻辑结构应该是( ) 。
A.队列
B.栈
C.线性表
D.有序表
答案:A
解释:解决缓冲区问题应利用一种先进先出的线性表,而队列正是一种先进先出的特殊线性表。 

双端队列 (Deque) 

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque是一个接口,使用时必须创建LinkedList的对象。 

 在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。   

public static void main(String[] args) {
        Queue<Integer> q = new LinkedList<>();//链式队列, 底层是双向链表
        Deque<Integer> deque1 = new ArrayDeque<>();//双端队列的线性实现,底层数组
        Deque<Integer> deque2 = new LinkedList<>();//双端队列的链式实现,底层是双向链表
        Deque<Integer> arrayStack1 = new ArrayDeque<>();//线性栈, 底层是数组
        Stack<Integer> arrayStack2 = new Stack<>();//线性栈, 底层是数组
        Deque<Integer> linkedStack1 = new LinkedList<>();//链式栈, 底层是双向链表
        LinkedList<Integer> linkedStack2 = new LinkedList<>();//链式栈,底层是双向链表
        LinkedList<Integer> linkedQueque = new LinkedList<>();//链式队列,底层是双向链表
    }

面试题

1. 用队列实现栈

OJ链接 

public class MyStack {
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;
    public MyStack() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }
    //入栈: 不为空的队列
    public void push(int x) {
        if (!qu1.isEmpty()) {
            qu1.offer(x);
        } else if (!qu2.isEmpty()) {
            qu2.offer(x);
        } else {//两个都为空, 放进第一个队列.
            qu1.offer(x);
        }
    }
    //出栈: 不为空的队列,出size-1个元素到另一个队列当中
    public int pop() {
        if (empty()) {
            return -1;//两个队列都为空了, 说明栈一定为空.
        }
        if (!qu1.isEmpty()) {
            int size = qu1.size();
            for (int i = 0; i < size - 1; i++) {
                /*Integer val = qu1.poll();
                qu2.offer(val);*/
                qu2.offer(qu1.poll());
            }
            //不能是以下的写法!!!每次size会变化.
            /*for (int i = 0; i < qu1.size() - 1; i++) {
                qu2.offer(qu1.poll());
            }*/
            return qu1.poll();
        } else {
            int size = qu2.size();
            for (int i = 0; i < size - 1; i++) {
                /*Integer val = qu2.poll();
                qu1.offer(val);*/
                qu1.offer(qu2.poll());
            }
            return qu2.poll();
        }
    }
    //peek
    public int top() {
        //注意和pop的区别.
        if (empty()) {
            return -1;//两个队列都为空了, 说明栈一定为空.
        }
        if (!qu1.isEmpty()) {
            int size = qu1.size();
            Integer val = -1;
            for (int i = 0; i < size; i++) {//要出所有的元素, val记录的最后一次出的元素就是栈顶元素
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        } else {
            int size = qu2.size();
            Integer val = -1;
            for (int i = 0; i < size; i++) {
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }
    //判断栈是否为空
    public boolean empty() {
        //两个队列都为空了, 说明栈一定为空.
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

2. 用栈实现队列

OJ链接 

public class MyQueue {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    //入队列: 往stack1栈入栈
    public void push(int x) {
        stack1.push(x);
    }
    //出队列:
    public int pop() {
        if (empty()) {//两个栈同时为空了, 说明此时队列一定为空.
            return -1;
        }
        if (stack2.empty()) {//如果stack2栈为空, 需要把stack1栈的所有元素入stack2栈
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();//要出stack2栈的栈顶元素 就是 出队列的元素
    }
    //查看队首元素:
    public int peek() {
        if (empty()) {//两个栈同时为空了, 说明此时队列一定为空.
            return -1;
        }
        if (stack2.empty()) {//如果stack2栈为空, 需要把stack1栈的所有元素入stack2栈
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();//查看stack2栈的栈顶元素 就是 队首元素
    }
    //判断队列是否为空
    public boolean empty() {
        //两个栈都为空了, 说明队列一定为空.
        return stack1.empty() && stack2.empty();
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 



 


 
  


 


 


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea的这款代码提示插件爱了

前言 Idea 是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序,Idea 还具有许多插件和扩展&#xff0c;可以根据开发人员的需要进行定制和扩展&#xff0c;从而提高开发效率,今天我们就来介绍一款…

Fourier分析入门——第7章——采样理论

目录 第 7 章 采样定理 7.1 引言 7.2 采样定理 7.3 错误识别(aliasing) 7.4 Parseval定理(Parseval[pzeifa:l]) 7.5 截断Fourier级数和回归理论(Truncated Fourier Series & Regression Theory) 第 7 章 采样定理 7.1 引言 在第 6 章中&#xff0c;我们发现有限区…

测试还是测开为你做个分析,让你少走弯路

软件测试和测试开发都是软件开发声明周期中非常重要的环节&#xff0c;缺一不可。当然了这两者还有区别的&#xff0c;比如果薪资待遇&#xff0c;开发要比测试待遇好&#xff0c;但测试里面功能测试和性能测试待遇又不同&#xff0c;掌握的技术不同、工龄都会有影响。 平均工…

一般小型企业,一个CRM系统要多少钱?都有哪些功能?

客户关系管理crm多少钱一套&#xff1f; 不同CRM要价不同&#xff0c;甚至同一款CRM产品在不同客户方部署下来的价格也是有差别的。 这篇给大家分享几款可实操的CRM管理软件的价位&#xff0c;有需要的可以做以参考&#xff01; 一、简道云CRM管理系统 模版地址&#xff1a;…

中国智造数据分析,预计2025年中国智能制造市场规模将达5.3万亿元

中国智造是制造强国建设主攻方向&#xff0c;也是中国制造公司可以提升核心竞争力的主要研究路径。智能制造成熟度水平能很好的衡量一个地区智能制造的发展水平。 分析全国成熟度二级及以上的企业数量分布&#xff0c;从图分析可以很直观的看出成熟度二级以上的企业数量主要分…

每日一题——三数之和(双指针)

每日一题 三数之和 题目链接 思路 解析函数原型 首先我们来看一下题目给的函数原型&#xff1a; int** threeSum(int* nums, int numsSize, int* returnSize, int**returnColumnSizes)题目要求我们返回一个二维数组&#xff0c;数组的行数代表着存在多少个满足条件的三元组&…

“向上管理”的7个最佳实践:如何管理你的老板?

向上管理是一种管理技巧&#xff0c;它指的是如何有效地管理你的老板。这种技巧可以帮助你更好地与老板沟通&#xff0c;提高工作效率&#xff0c;增加工作成就感。本文将介绍七个最佳实践&#xff0c;帮助你学会如何向上管理。 1. 了解老板的需求和期望 了解老板的需求和期望…

R语言结构方程模型(SEM)在生态学领域中的实践应用

结构方程模型&#xff08;Sructural Equation Model&#xff09;是一种建立、估计和检验研究系统中多变量间因果关系的模型方法&#xff0c;它可以替代多元回归、因子分析、协方差分析等方法&#xff0c;利用图形化模型方式清晰展示研究系统中变量间的因果网络关系&#xff0c;…

Velocity不用愁!Velocity系统的前端工程化之路 | 京东云技术团队

Velocity是一个基于Java的Web页面模版引擎。十多年前&#xff0c;Velocity将Java代码从Web页面中分离出来&#xff0c;使得开发者能够并行网页开发和Java开发。随着十年前后端分离的浪潮涌动&#xff0c;回首再面对这些基于Velocity的旧系统&#xff0c;无论是后端还是前端人员…

了解信号的传输方式、编码与调制、信道的极限容量

1.了解信号的传输方式、编码与调制、信道的极限容量 笔记来源&#xff1a; 湖科大教书匠&#xff1a;传输方式 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 1.1 了解信号的传输方式 串行传输与并行传输 同步传输与异步传输 为什么需要收发双发…

NetApp 数据存储系统 AFF A 系列的优势及应用行业

AFF A 系列阵列&#xff1a;云集成、性能极强、蓄势待发 需要小幅&#xff08;或大幅&#xff09;提升您的关键业务应用程序的性能吗&#xff1f;我们的 AFF A 系列阵列具备屡获殊荣的速度和响应能力&#xff0c;能满足性能敏感型工作负载的需求 为什么选择 NetApp AFF A 系列…

相关、匹配滤波、脉冲压缩以及模糊函数

文章目录 【 1.相关 】自相关互相关 【 2.匹配滤波 】滤波器模型有色噪声 时滤波器的特性白噪声 时滤波器的特性 【 3.脉冲压缩】时域脉冲压缩频域脉冲压缩 【 4.模糊函数 】【 5.四者之间的关系 】相关和卷积之间的关系 【 6.参考文献 】 【 1.相关 】 相关性表示一个信号平移…

【Docker系列】Dockerfile 中指令作用介绍

前言 当今容器技术已经成为了现代应用程序开发和部署的重要工具&#xff0c;Docker 作为当前最受欢迎的容器平台之一&#xff0c;提供了高效、轻量级的容器解决方案。而 Dockerfile&#xff0c;则是定义 Docker 容器镜像构建过程的文件&#xff0c;它包含了所有构建该镜像所需…

python大作业——学生管理系统制作,另赠福利:GUI学生管理系统源码

目录 前言环境使用:代码展示尾语 &#x1f49d; 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 环境使用: Python 3.8 解释器 Pycharm 编辑器 可领取福利: GUI学生管理系统源码 python资料、源码、教程\福利皆: 点击此处跳转文末名片获取 代码展示 从学生信息数据库…

python+django植物园性毒源成分管理系统

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括植物性毒源成分管理系统的网络应用&#xff0c;在外国植物性毒源成分管理系统已经是很普遍的方式&#xff0c;不过国内的植物性毒源成分管理可能还处于起步阶段。植物性毒源成…

实验4—OpenGL的鼠标交互绘制

一、实验目的 1.掌握OpenGL的鼠标按钮响应函数。 2.掌握OpenGL的鼠标移动响应函数。 3.进一步巩固OpenGL的基本图元绘制基础 二、实验内容 1.鼠标画草图——实现鼠标点到哪&#xff0c;线就画到哪。 思路&#xff1a; 1 )在主程序注册鼠标响应和鼠标移动子函数: glutMouseF…

ChatGPT:你真的了解网络安全吗?浅谈网络安全攻击防御进行时之网络安全新防御

ChatGPT&#xff1a;你真的了解网络安全吗&#xff1f;浅谈网络安全攻击防御进行时 网络安全新防御1. 针对人工智能2. 针对5G和物联网3. 针对云安全4.针对社交工程5. 针对加密技术6. 针对多层次的安全控制 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Tra…

针对Rokcetmq引入的缺点提供相应解决方案

1.针对Rokcermq引入可用性降低问题 Rocketmq实现高可用模式&#xff0c;Rocketmq有三种模式&#xff1a;单机模式、主从模式、分片集群模式。 单机模式 单机模式&#xff0c;就是 Demo 级别的&#xff0c;一般就是你本地启动了玩玩儿的&#xff0c;没人生产用单机模式。 docker…

Postman传递@requestbody标注的List集合的传参遇到的问题

Postman传递requestbody标注的List集合的传参遇到的问题 引子如何测试以及遇到的问题参考文献 引子 我们想测试如下接口 RequestMapping(value "saveMessageRecover", method RequestMethod.POST) ResponseBody public AjaxMessage saveMessageRecover(RequestBod…

聚观早报 |必应成为中国第一大桌面搜索引擎;快手上市后首次盈利

今日要闻&#xff1a;必应成为中国第一大桌面搜索引擎&#xff1b;快手上市后集团层面首次盈利&#xff1b;ChatGPT相关诈骗攻击与日俱增&#xff1b;比亚迪回应法国建厂传闻&#xff1b;薇娅夫妇半年收获两家上市公司 必应成为中国第一大桌面搜索引擎 5 月 22 日消息&#xf…