当前位置: 首页> 游戏> 攻略 > Java 栈(Stack)与队列(Queue)

Java 栈(Stack)与队列(Queue)

时间:2025/8/23 10:56:19来源:https://blog.csdn.net/lllsure/article/details/140389537 浏览次数:0次

一.栈(Stack)

1.概念

是一种特殊的线性表,遵循“先进后出”的原则。进栈就是向栈内放入元素,元素放在栈顶;出栈就是删除栈顶元素。

2.栈的方法

实例化栈:

这是int类型的例子

Stack<Integer> s = new Stack<>();

方法:

方法功能
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空

3.栈的模拟实现

栈的模拟实现主要是用数组,定义一个标记,记录数组使用的长度。模拟的就是上面的5个方法。

3.1 push

数组从头开始用,入栈一个就是在数组后面加一个

public int push(int e){array[size++] = e;return e;
}

3.2 pop

直接让size--即可,后面本来有的数据(就是array[size-1])会被后来入栈的元素覆盖

public int pop(){int e = array[size-1];size--;return e;
}

3.3 peek

思路与pop无异,只是不用size--而已

public int peek(){if(empty()){throw new RuntimeException("栈为空,无法获取栈顶元素");}return array[size-1];
}

3.4 size

public int size(){return size;
}

3.5 empty

public boolean empty(){return 0 == size;
}

4.栈的经典例题

4.1 经典括号对应问题

对应题目LeetCode:20. 有效的括号

这个无需多言,遍历整个字符串,遇到左括号放入栈,遇到右括号与栈顶元素匹配。

class Solution {public boolean isValid(String s) {Stack<Character> stack=new Stack<>();for(int i=0;i<s.length();i++){char ch=s.charAt(i);if(ch=='(' || ch=='[' || ch=='{'|| i==0){stack.push(ch);}else{if(stack.empty()==false){char chp=stack.peek();if(chp=='(' && ch==')'){stack.pop();}else if(chp=='{' && ch=='}'){stack.pop();}else if(chp=='[' && ch==']'){stack.pop();}else{return false;}}else{return false;}}}if(stack.empty()){return true;}else{return false;}}
}

这个可以说是括号问题的元问题,其他的括号问题都是其变种,万变不离其宗,本质上还是栈的问题。

4.2 表达式求值

表达式分为三种:前缀表达式、中缀表达式和后缀表达式(逆波兰式)。

不管怎么变,本质都是栈问题。

我们平常写的数学表达式是中缀表达式,这里着重说明后缀表达式也就是逆波兰式表达式。

这里对应的题目:LeetCode 150. 逆波兰表达式求值

怎么将中缀转成后缀呢?

第一步,先加括号,在中缀表达式的基础上继续加,如图上加的彩色括号

第二步,将一个括号内的运算符移出,图上标记了该运算符是从那个括号里移出的

第三步,把括号全删了就成了后缀表达式

明白了转化的原理,不难发现这就是一个栈问题。

我们可以从头遍历后缀表达式,数字入栈,遇到运算符两个数字出栈运算。这里注意两个数字是有顺序的,后出栈的是运算符前面的数。运算完得到的新数入栈,直到后缀表达式遍历完,返回栈顶元素即可。

class Solution {public int evalRPN(String[] tokens) {Stack<Integer> stack=new Stack<>();int val1=0;int val2=0;for(String str : tokens) {switch(str) {case "+" :val2 = stack.pop();val1 = stack.pop();stack.push(val1+val2);break;case "-" :val2 = stack.pop();val1 = stack.pop();stack.push(val1-val2);break;case "*" :val2 = stack.pop();val1 = stack.pop();stack.push(val1*val2);break;case "/" :val2 = stack.pop();val1 = stack.pop();stack.push(val1/val2);break;default:stack.push(Integer.parseInt(str));break;}}return stack.pop();}}

5.栈、虚拟机栈、栈帧有什么区别

名称描述举例
一种具有后进先出特性的数据结构,用于存储临时数据、函数调用信息、局部变量等程序执行函数调用时,新函数调用信息压入栈
虚拟机栈Java 虚拟机中的运行时数据区,用于存储方法调用状态,每个线程有独立的虚拟机栈多线程 Java 程序中,各线程执行方法时在其虚拟机栈操作
栈帧虚拟机栈中的存储单元,与正在执行的方法对应,方法调用时创建,执行完弹出Java 方法调用时创建对应栈帧存储执行数据

二.队列(Queue)

1.概念

一种特殊的线性表,与栈不用的是,队列遵循“先进先出”的原则,队尾入队,队头出队。

注意:在Java中,Queue是个接口,底层是通过链表实现的,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

Queue<Integer> queue = new LinkedList<>();

2.队列的方法

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

3.队列的模拟实现

队列的模拟实现可以用数组,也可以用双向链表。

这里使用的是双向链表。

3.1 offer

public void offer(int e){ListNode newNode = new ListNode(e);if(first == null){first = newNode;}else{last.next = newNode;newNode.prev = last;}last = newNode;size++;
}

这里的first是队头,last是队尾,下面也都是这样

3.2 poll

public int poll(){
// 1. 队列为空
// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除int value = 0;if(first == null){return null;}else if(first == last){last = null;first = null;}else{value = first.value;first = first.next;first.prev.next = null;first.prev = null;}--size;return value;
}

3.3 peek

public int peek(){if(first == null){return null;}return first.value;
}

3.4 size

public int size() {return size;
}

3.5 isEmpty

public boolean isEmpty(){return first == null;
}

4.循环队列

在用数组模拟队列时,如果只是单纯删一个first++,加一个las--的话会太浪费空间。如果我们让空间变成一个环的话,空间就可以得到循环利用。

那么怎么让一个数组变成一个环呢?

这里的变环不是“物理”的变环,而是通过一个技巧。

假如一个数组的长度是8,如果成环的话,那么下标7的下一个下标就是0了,我们只要(n+1)%arr.length即可(n是当前数组的下标,arr是整个数组)。这种思想在一些算法题中也有使用。

循环队列对应的例题:LeetCode 622. 设计循环队列

上面只是解决了怎么成环的问题,在设计循环队列时有一个很重要的点就是怎么判断队列是否为空、是否为满。这里有三个解决方法:

一是用一个size记录现在用了多少个位置;二是用boolean数组标记(锁上),这个也是做算法题的惯用套路;三是我用的,留下一个位置空着不用。

用第三种判断是不是满了直接看rear(后面)的下一个的下一个是不是front(前面);判断是不是空直接判断rear是不是等于front。

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

三.双端队列((Deque)

双端队列的两端都支持出队入队,可以说是队列的进化版。

同时注意:Deque也是一个接口,使用时必须创建LinkedList的对象。

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

Deque<Integer> stack = new ArrayDeque<>();
Deque<Integer> queue = new LinkedList<>();

双端队列的方法:

  • addFirst() - 在双端队列的开头添加指定的元素。如果双端队列已满,则引发异常。

  • addLast() - 在双端队列的末尾添加指定的元素。如果双端队列已满,则引发异常。

  • offerFirst() - 在双端队列的开头添加指定的元素。如果双端队列已满,则返回false。

  • offerLast() - 在双端队列的末尾添加指定的元素。如果双端队列已满,则返回false。

  • getFirst() - 返回双端队列的第一个元素。如果双端队列为空,则引发异常。

  • getLast() - 返回双端队列的最后一个元素。如果双端队列为空,则引发异常。

  • peekFirst() - 返回双端队列的第一个元素。如果双端队列为空,则返回null。

  • peekLast() - 返回双端队列的最后一个元素。如果双端队列为空,则返回null。

  • removeFirst() - 返回并删除双端队列的第一个元素。如果双端队列为空,则引发异常。

  • removeLast() - 返回并删除双端队列的最后一个元素。如果双端队列为空,则引发异常。

  • pollFirst() - 返回并删除双端队列的第一个元素。如果双端队列为空,则返回null。

  • pollLast() - 返回并删除双端队列的最后一个元素。如果双端队列为空,则返回null。

关键字:Java 栈(Stack)与队列(Queue)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: