栈来了(包括概念,特性,应用,模拟实现...)你要看到的都在这里~

1 .概念及特性

:是一种特殊的线性表结构;

特殊性表现在:它只允许在固定的一端进行数据的插入与删除操作,将能进行数据操作的一端称为栈顶,而另一端称为栈底。

压栈:栈的插入操作叫做进栈/压栈/入栈;
出栈:栈的删除操作叫做出栈;

出入数据都是在栈顶;

栈在生活中实例

最后放的羽毛球最先会被拿出来
在这里插入图片描述

栈的特性:遵循 后进先出 的原则,LIFO(Last In First Out);
大神形容:吃进去吐出来

2.常见方法

在这里插入图片描述
代码如下

package day20211018;
import java.util.Stack;
public class TestStack {
    //  测试栈
    public static void main(String[] args) {
        //Stack 是一个类
        Stack<Integer> s=new Stack();
        // 压栈:1 2 3 4
        s.push(1);
        s.push(2);
        s.push(3);
        s.push(4);
        System.out.println(s); //[1, 2, 3, 4]
        // 获取栈顶元素
        System.out.println(s.peek()); //4
        //获取栈中有效元素的个数
        System.out.println(s.size());  //4
        // 出栈 :两个元素
        s.pop();
        s.pop();
        System.out.println(s);  //[1, 2]
        //判断栈是否为空
       if(s.empty()) {
           System.out.println("栈为空");
       }else{
           System.out.println("栈不为空");
       }
    }
}

3.栈的应用

  • 改变元素的序列

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

解析

根据 的特性:后进先出,所以选择 D

  • 将递归转化为循环
 // 递归方式
    public void reversePrintList(Node node){
        if(null = node){
           return ;
        }
        reversePrintList(node.next);
       System.out.print(node.val + " ");
        }
    }
    // 循环方式
   public void reversePrintList(Node node){
        if(null == node){
            return;
        }

        Stack<Node> s = new Stack<>();
        // 将链表中的结点保存在栈中
        Node cur = node;
        while(null != cur){
            s.push(cur);
            cur = cur.next;
        }

        // 将栈中的元素出栈
        while(!s.empty()){
            System.out.print(s.pop().val + " ");
        }
    }

思考

为什么使用栈将递归转化为循环,而没有采用其他结构呢?

主要原因:递归调用遵循的是,后调用的先退出,先调用的后退出,这种特性正好与栈的特性相匹配;但并不是所有的递归转化为循环时都需要栈,比如:求阶乘;

题目描述:给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

在这里插入图片描述
解题思路

(1)对所给字符串依次进行遍历,然后拿到其中的每个元素(括号);
(2)对拿到的元素进行判断:

  如果是左括号:入栈(push)
  如果是右括号:与栈顶元素进行比对
  注意:要保证栈中有元素

比对结果:

     匹配:将栈顶元素进行出栈返回;
    不匹配:返回 false

代码如下

class Solution {
    public boolean isValid(String s) {
       //构造一个栈
       Stack<Character> st=new Stack<>();
      //1.遍历字符串
      for(int i=0;i<s.length();i++){
          //判断第i个元素是左括号还是右括号
          char c=s.charAt(i); //获取i个字符
          if(c =='(' || c == '[' || c == '{'){
             // 获取到的元素为左括号---入栈
             st.push(c);
          }else{
              //获取到的元素为右括号
              //得判断栈中是否有元素
              if(st.empty()){
                  return false;
              }

              //获取栈顶元素
              char top=st.peek();
              if((top == '(' && c == ')')  ||
                 (top == '[' && c == ']')  ||
                 (top == '{' && c == '}')){
                     st.pop();
                     continue;
                 }else{
                     return false;
                 }
          }
      }
        //最后判断栈是否为空
        //不空说明栈中还有元素
        if(!st.empty()){
            return false;
        }else{
            return true;
        }
    }
}
  • 逆波兰表达式求值

逆波兰表达式又称为后缀表达式,所谓后缀表达式就是将运算符放置在操作数之后的一种方式;
假设:要求(4-2)*(5+3)的值,对于我们而言,可以借助括号来进行辅助运算,而对于计算机而言,是看不到括号的,因此,为了使计算机能够帮助我们求解更加复杂的数学问题,就引入了后缀表达式;
对于上述算式转成后缀表达式就变成了如下所示:
4 2 - 5 3 + *

接下来看问题描述
LeetCode逆波兰表达式求值

根据逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式

说明:

整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

解题思路

(1)依次遍历表达式中的每一项,然后进行判断;
(2)如果是数字就入栈,是运算符,就依次取出栈顶的两个元素进行运算,最后将计算结果入栈;
(3)将栈顶的最终计算结果返回;

代码如下

class Solution {
    public int evalRPN(String[] tokens) {
        //构造一个栈
        Stack<Integer> s=new Stack<>();
        //遍历表达式中的每一项
        for(String e: tokens){
            //进行判断
            if(!((e.equals("+")) || (e.equals("-")) || (e.equals("*")) || (e.equals("/")))){
              //此时该项为数字---入栈
              //e是数字字符串,需转成数字,才能入栈
              s.push(Integer.parseInt(e));
            }else{
                //为字符串
                //从栈顶取出两个操作数进行运算
                int right=s.pop();  //该数字为右操作数
                int left=s.pop();  //该数字为左操作数
                switch(e){
                    case "+":
                      s.push(left + right);
                       break;
                    case "-":
                       s.push(left - right);
                        break;
                    case "*":
                        s.push(left * right);
                         break;
                    case "/":
                         s.push(left / right);
                         break;
                    default:
                         break;
                }
            }
        }
      return s.peek();
    }
}
  • 出栈入栈次序匹配

栈的压入与弹出

题目描述: 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列

代码如下

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
     Stack <Integer> s = new Stack();
    int pushIdx = 0, outIdx = 0;
    while(outIdx < popped.length){
        //当栈空或者栈顶元素与待出元素不相等时候入栈
        while(s.empty() || s.peek() != popped[outIdx]){
            //有元素时,继续入栈
            if(pushIdx < pushed.length){
                s.push(pushIdx);
                pushIdx++;
            }else {
                return false;
            }
        }
        s.pop();
        outIdx++;
    }
    return true;
}
}

4.栈的模拟实现

package day20211023;

import java.util.Arrays;
import java.util.EmptyStackException;


public class Stack <E>{
    //模拟实现栈
    //删减版的顺序表
    E[] array;
    int size;  //用来表示栈中元素的个数

    //构造方法
    public Stack(){
       array=(E[]) new Object[10] ;
       size=0; //没有元素时
    }

    //压栈--push
    public E push(E e){
        //确保栈空间足够
        ensureCapacity();
        array[size]=e;
        size++;
        return e;

    }

    //出栈
    public E pop(){
        //判断栈是否为空
        if(empty()){
            throw new RuntimeException("栈为空异常");
        }
        E ret=peek();
        size--;
        return ret;
    }

    //获取栈顶元素
    public E peek(){
        if(empty()){
            throw new RuntimeException("栈为空异常");
        }
        return array[size-1];
    }

    //判断栈是否为空
    public boolean empty(){
        return 0==size;
    }

    //获取栈的大小
    public int size(){
        return size;
    }

    //栈容量
    private void ensureCapacity(){
        if(size==array.length){
            int newCapacity=size*2;
            array=Arrays.copyOf(array,newCapacity);
        }
    }


    public static void main(String[] args) {
        Stack<Integer> s=new Stack<>();
        //一般不进行遍历操作
       //压栈:1 2 3 4
        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();
        s.pop();
        System.out.println(s.peek());  //2
        if(s.empty()){
            System.out.println("栈为空");
        }else{
            System.out.println("栈不为空");
        }
    }
}

区分四个概念— 栈 & 虚拟机栈 & 栈帧 & 操作数栈

栈:指的是一种后进先出的数据结构,在Java集合中有对应的实现–就是 StackStack 在实现时继承了vector

虚拟机栈

虚拟机栈(Java Virtual Machine Stacks)是一块具有特殊作用内存空间,它的生命周期与线程相同;而JVM为了更便于管理数据,将内存按照不同的需求划分有栈区、 堆区
栈区:线程是私有的,栈区中存放的是函数调用相关的一些信息,主要是栈帧,栈帧也是按照数据结构中栈的特性来进行实现的;当栈区中内存空间不足时,会抛出StackOverFLowException 异常信息。
堆区:一般 new 出来的空间都在堆区;

栈帧

栈帧(Stack Frame)是用于支持 Java 虚拟机进行方法调用和执行的数据结构,每个方法在执行时,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,当方法调用结束时,该方法对应的栈帧会从虚拟机中出栈。

操作数栈

操作数栈(Operand Stack)也被称为操作栈,它也是一个后进先出的(Last In First out,LIFO)栈。同局部变量表一样,操作数栈的最大深度在编译时就被确定下来了,操作数栈的每一个元素可以是任意的Java数据类型,包括 longdouble

注意:

每个方法中栈帧结构是一样的,大小可能不同,栈帧的大小在程序编译时就已经确定好了大小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值