目录
一.栈(stack)的基本概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
- 栈顶(Top):线性表允许进行插入和删除的一端。
- 栈底(Bottom):固定的,不允许进行插入和删除的另一端。
- 空栈:不含任何元素。
如上图:a1为栈底元素,an为栈顶元素。由于栈只能在栈顶进行插入和删除操作,故进栈次序依次为a1,a2,... ,an 而出栈次序为an,...,a2,a1。 栈的明显的操作特征为后进先出(Last In First Out,LIFO),故又称 后进先出的线性表。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则,即后进来的元素先出去,先进来的元素后出去
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据在栈顶。
注:Push为入栈,Pop为出栈
二.栈的使用
方法 | 功能 |
---|---|
Stack() | 构造一个空的栈 |
E push(E e) | 将e入栈,并返回e |
E pop() | 将栈顶元素出栈并返回 |
E peek() | 获取栈顶元素 |
int size() | 获取栈中有效元素个数 |
boolean empty() | 检测栈是否为空 |
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(12);//入栈
stack.push(23);
stack.push(34);
stack.push(45);
stack.pop();//出栈,删除栈顶的元素
int x = stack.peek();//获取栈顶的元素,但不删除
boolean flg = stack.empty();//检测栈的元素是否为空,不为空返回false
System.out.println(flg);
}
三.模拟实现栈
package MyStackDemo;
import java.util.Arrays;
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[5];
}
/**
* 判断栈是否满了
* @return 满了返回true,否则返回false
*/
public boolean isFull(){
return this.usedSize == this.elem.length;
}
/**
* 入栈
* @param val
*/
public void push(int val){
if(isFull()){
//扩容
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
this.elem[usedSize] = val;
this.usedSize++;
}
/**
* 判断栈是否为空栈
* @return
*/
public boolean empty(){
return this.usedSize == 0;
}
/**
* 出栈
* @return
*/
public int pop(){
if(empty()){
throw new RuntimeException("栈为空");
}
int ret = elem[usedSize-1];
// 如果元素时引用类型,需要 elem[usedSize-1]=null;
usedSize--;
return ret;
}
/**
* 获取栈顶元素但不删除
* @return
*/
public int peek(){
if(empty()){
throw new RuntimeException("栈为空");
}
return elem[usedSize-1];
}
public int getUsedSize(){
return this.usedSize;
}
}
四.栈的应用场景
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
答案是: C B
2.将递归转化为循环
比如:逆序打印链表
//递归
public void diasplay(ListNode head){
if(head == null){
return ;
}
if(head.next == null){
System.out.println(head.val);
return ;
}
display(head.next);
System.out.println(head.val);
}
//循环 (通过栈)
public void diasplay(ListNode head){
if(head == null){
return ;
}
Stack<ListNode> stack = new stack<>();
// 将链表中的结点保存在栈中
ListNode cue = head;
// 将链表里的元素入栈
while(cur != null){
stack.push(cur);
cur = cur.next;
}
// 将栈中的元素出栈
while(!stack.empty()){
ListNode p = stack.pop();
System.out.println(p.val);
}
}
3.逆波兰表达式求值
- 根据 逆波兰表示法,求表达式的值。
- 有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
- 注意 :两个整数之间的除法只保留整数部分。
- 可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
我们首先发现,token字符串构成了一个后缀表达式,这里我们有必要先来说明一下后缀表达式
和中缀表达式的转化
中缀表达式转后缀表达式
为什么转:转成后缀表达式,机器更好计算
怎么转:
- 先根据运算符的优先级 加括号
- 再把运算符号移到括号的后边(括号外)
- 最后再把括号去掉
例如:
中缀表达式 a+b*c+(d*e+f)*g 转化 成 后缀表达式 abc* + de*f + g*+这就是我们上面讲的步骤
- 先根据运算符的优先级 加括号
- 再把运算符号移到括号的后边(括号外)
- 最后再把括号去掉
那么怎么计算后缀表达式? 用栈
遍历这一组数据,如果是数字,就入栈,
遇到运算符号时,弹出栈顶的两个元素,这两个数作运算
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
//1.遍历这个字符串,如果是数字就入栈,不是就出栈
for(int i = 0 ;i<tokens.length;i++){
String x = tokens[i];
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();
}
public boolean isOperation(String S){
if(S.equals("+") || S.equals("-") || S.equals("*") || S.equals("/") ){
return true;
}
return false;
}
}
注:需要强调的是
别忘了:先出栈的时运算符的右操作数,后出栈的是左操作数
4.括号匹配(有效的括号)
给定一个只包括 ' ( ' , ' ) ',' { ', ' } ',' [ ',' ] ' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
四种情况:
不匹配: ( ( ) 左括号多
( ))右括号多
( ] 左右括号不匹配
匹配
最后一个左括号与第一个右括号匹配 则匹配
1.左括号入栈
2.遇到右括号就和 当前栈顶元素 相比较。如果匹配则出栈,继续遍历
字符串,不匹配则结束
3.字符串遍历完成后,栈中还有元素,那么说明左括号多
4.栈为空时,字符串还没有遍历完,那么右括号多
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 == '{'){
//左括号则入栈
stack.push(ch);
}else{
if(stack.empty()){
//右括号比左括号多
return false;
}else{
char c = stack.peek();
if(c=='('&&ch==')' || c=='['&&ch==']' || c=='{'&&ch=='}'){
stack.pop();//当前这两个括号是匹配的
}else{
//当前两个括号不匹配
return false;
}
}
}
}
if(!stack.empty()){
//左括号比右括号多
return false;
}
return true;
}
}
5.栈的压入、弹出序列
栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
这就是我们的思路
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length ==0 || popA.length ==0) return false;
Stack<Integer> stack = new Stack<>();
int j = 0;//遍历popA数组
for(int i = 0; i<pushA.length;i++){
int val = pushA[i];
stack.push(val);
while(!stack.empty() && j< popA.length && stack.peek()==popA[j]){
stack.pop();
j++;
}
}
if(stack.empty()){
return true;
}
return false;
}
}
五.栈、虚拟机栈、栈帧有什么区别呢
- 栈:数据结构 描述 和 组织 这个数据的
- Java虚拟机栈:一块内存【局部变量】
- 栈帧:函数调用的时候,在Java虚拟机栈上给这个函数开辟的一块内存空间
这里我们就简单描述一下啦~