数据结构:栈

特点

先进后出(FILO)后进先出(LIFO)

现在我有一个栈,栈顶不封顶,可以将元素加进来,但是栈底是封闭的,元素没办法溜走栈底 

把元素加进去,想要把12拿出来的时候,必须让23和34分别出来,12才能从栈底脱身


手搓一个栈

我们先用数组来实现一个栈,它的时间复杂度是O(1)

🆗了解了这些方法之后,我们把这些方法写到接口里面

//IStack 接口
package stack;

public interface IStack {
    void push(int x);

    int pop();

    int peek();

    int size();

    boolean empty();

    boolean full();
}

在具体实现的文件里面实现方法

package stack;

public class MyStack implements IStack{
    //初始化
    private int[] elem;
    private int usedSize;
    private static final int DEFAULT_CAPACITY = 10;
    public MyStack(){
        elem = new int[DEFAULT_CAPACITY];
    }
    @Override
    public void push(int x) {


    }

    @Override
    public int pop() {
        return 0;
    }

    @Override
    public int peek() {
        return 0;
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean empty() {
        return false;
    }

    @Override
    public boolean full() {
        return false;
    }
}

push 压栈

压栈之前首先要判断栈是不是满的

    @Override
    public boolean full() {
        if(usedSize == elem.length){
            return true;
        }
        return false;
    }

满了的话就需要扩容,然后把数组对应位置放入值x就行

    @Override
    public void push(int x) {
        if (full()){
            elem = Arrays.copyOf(elem, 2* elem.length);
        }
        elem[usedSize] = x;
        usedSize++;
    }

pop出栈

弹出元素需要先判断栈内为不为空

    @Override
    public boolean empty() {
        return usedSize == 0;
    }

如果为空了,可以抛一个异常

        if(empty()){
            throw new EmptyException("栈空了!")
        }
//重新写一个文件反馈异常
public class EmptyException extends RuntimeException{
    public EmptyException(String msg){
        super(msg);
    }
}

删除元素 --》usedSize--  就行了

    @Override
    public int pop() {
        if(empty()){
            throw new EmptyException("栈空了!")
        }
        int old = elem[usedSize-1];
        usedSize--;//相当于删除元素
        return old;
    }

peek和size

    @Override
    public int peek() {
        return elem[usedSize-1];
    }

    @Override
    public int size() {
        return usedSize;
    }

用链表实现一个栈

如果是单链表,我们没有last这个引用

假设从头入栈-》O(1),从头出:删除头节点 -》O(1)

假设从尾巴入栈 --》 O(n)   从尾巴出--》O(n)

如果是双向链表,可以直接当成一个栈,叫做链式栈


有关栈的题目和应用

开胃小菜

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

answer:C

要先弹出3,栈里面必然有1和2,然而当3弹出来后,1被2挡着,不可能比2更快弹出

所以C就是不对的

看看B

1和2先入栈,2先出栈,再入栈3,3出栈,再入栈4,4出栈最后1出栈

顺序就是2,3,4,1

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

3.应用栈来逆序打印链表

循环方法

用一个cur遍历链表,每次遍历一个元素就加入栈中,把元素一一出栈,出栈的过程中顺便打印

递归方法

比循环多这么一步:每次入栈就调用一次自己的方法

    // 递归方式
    void printList(Node head){
        if(null != head){
            printList(head.next);
            System.out.print(head.val + " ");
        }
    }
    // 循环方式
    void printList(Node head){
        if(null == head){
            return;
        }
        Stack<Node> s = new Stack<>();
// 将链表中的结点保存在栈中
        Node cur = head;
        while(null != cur){
            s.push(cur);
            cur = cur.next;
        }
        // 将栈中的元素出栈
        while(!s.empty()){
            System.out.print(s.pop().val + " ");
        }
    }

150. 逆波兰表达式求值 - 力扣(LeetCode)

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*' 和 '/' 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

提示:

  • 1 <= tokens.length <= 104
  • tokens[i] 是一个算符("+""-""*" 或 "/"),或是在范围 [-200, 200] 内的一个整数

什么叫做逆波兰表达式

逆波兰表达式是一种后缀表达式

我们平常写的算式是中缀表达式 比如:9+(3-1)*3+8/2

改成后缀表达式就是 9 3 1 - 3 * + 8 2 / +

中缀怎么变后缀的呢?

我们的中缀表达式遵循先乘除后加减的原则

我们就先给乘除操作加上一个括号

9先加上乘法的结果,再给他们俩一个括号

最后整个加在一起,再给整体一个括号

现在把每个运算符移到对应括号的外边,注意只用移动一层就行

然后把所有的括号删掉就是后缀表达式了

9 3 1 - 3 * + 8 2 / +

那后缀表达式怎么运行/计算的呢?

1.把符号前面数字扔到栈里面,符号不要扔进去!

2.现在栈里面有9,3和1,把先弹出来的1放到减号的右边,紧接着弹出来的3放到减号左边

3.3-1 = 2 把这个计算得出的2再压回栈中

现在栈里面长这样

4.再把3扔进去

5.弹出3放*右边,弹出2放*左边,计算得到的6再压入栈里面

6.加号放栈外边,依次弹出6和9,计算得到15再压入栈

7.把8和2依次压入栈,然后2和8依次弹出分别放除号右边和左边 

8.计算得到4再压回栈里

现在栈的情况

 

最后把这俩依次弹出放到+右边和左边再进行计算得到19,这个19就是最终的答案啦

🆗了解了后缀表达式怎么计算,那我们回到题目

这道题就是要我们用代码实现上面的过程

第一步:我们把数字(Integer)压入栈中,同时要设计一个方法判断是不是Integer

    private boolean isOperation(String s){
        if(s.equals("+") || s.equals("-")||s.equals("*")||s.equals("/")){
            return true;
        }
        return false;
    }
        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();//注意先弹出num2,方便后面把num2放到右边
                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;
                }
            }

第三步:把最终的计算结果pop出来就行

整个的代码

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();//注意先弹出num2,方便后面把num2放到右边
                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();
    }

    private boolean isOperation(String s){
        if(s.equals("+") || s.equals("-")||s.equals("*")||s.equals("/")){
            return true;
        }
        return false;
    }
}


20. 有效的括号 - 力扣(LeetCode) 

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

 括号不匹配的情况(我们只需要解决的情况)

遇到的第一个右括号应该和最后一个左括号进行匹配,所以我们要用到栈

先把左括号入栈,遇到右括号的时候和栈顶元素的左括号比较是不是匹配的,每匹配一个就pop一个

当栈里面的元素为空且字符串也遍历完成时,匹配完成

情况1:

情况2:

情况3:

完整的代码


栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com) 

描述

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

1. 0<=pushV.length == popV.length <=1000

2. -1000<=pushV[i]<=1000

3. pushV 的所有数字均不相同

示例1

输入:

[1,2,3,4,5],[4,5,3,2,1]

返回值:

true

说明:

可以通过push(1)=>push(2)=>push(3)=>push(4)=>pop()=>push(5)=>pop()=>pop()=>pop()=>pop()
这样的顺序得到[4,5,3,2,1]这个序列,返回true      

示例2

输入:

[1,2,3,4,5],[4,3,5,1,2]

返回值:

false

说明:

由于是[1,2,3,4,5]的压入顺序,[4,3,5,1,2]的弹出顺序,要求4,3,5必须在1,2前压入,且1,2不能弹出,但是这样压入的顺序,1又不能在2之前弹出,所以无法形成的,返回false      

 

我们设置两个数组,一个是压栈的数组,一个是用来判断的数组

设置i遍历push里面的元素,每次遍历一个元素看超不超过pop里面的j位置的元素的大小

设置j遍历pop里面的元素,遍历一个就看看与栈里面元素是不是一样的,一样的就把栈里面对应元素pop出来

 如图,pop第一个元素是4,那么push里面1 2 3 4可以直接push到栈里面,4一样,弹出,再把5push进去,j往后走。5一样,弹出。再依次比较3 2 1

push和栈都是空的,说明匹配完成

归纳步骤:

1.遍历push数组,把元素放入栈中

        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for(int i = 0; i < pushV.length; i++){
            stack.push(pushV[i]);

2.每push一个元素,就和pop数组的元素比较

3.如果相等,j++且出栈(注意:这么做的前提是栈不为空且数组不越界)

4.如果不相等,就想办法入栈

            while(!stack.empty() && j < popV.length && stack.peek() == popV[j]){
                stack.pop();
                j++;
            }

完整代码


155. 最小栈 - 力扣(LeetCode)

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

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

示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

 这道题只用一个栈是明显行不通的

假设有这么一组数,-1,2,6,3,依次入栈,想要最快拿到最小值-1就得经过O(n)时间,不合题意

所以我们可以申请两个栈,一个栈就是普通的栈,用来放列表的元素的

另一个栈就是最小栈,每次放入普通栈的元素都要和原来栈里面的元素进行比较,如果是最小的话就放入最小栈,放到最后你会发现,最小栈的栈顶就是那个最小值

初始化

    private Stack<Integer> stack;
    private Stack<Integer> minStack;
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }

push

普通的栈一定得放元素

最小栈如果是空的,也要放;如果不为空且要存放的元素小于最小栈的栈顶,也要放到最小栈

    public void push(int val) {
        //普通栈放元素
        stack.push(val);
        //最小栈空不空
        if(minStack.empty()){
            minStack.push(val);
        }else{
            //判断要存放的元素是否小于栈顶元素
            int peekVal = minStack.peek();
            //相同元素也要压入最小栈
            if(val<=peekVal){
                minStack.push(val);
            }
        }
    }

pop

1.要pop的元素和栈顶元素比较

2.如果pop的元素和栈顶元素是一样的,那么两个栈都要出

3.不一样只出普通栈

    public void pop() {
        int val = stack.pop();
        if(!minStack.empty()){
            if(val == minStack.peek()){
                minStack.pop();
            }
        }
    }

top和getMin

    //peek获取当前普通栈的栈顶元素
    public int top() {
        return stack.peek();
    }
    //最小栈的peek,通过这个方法获取最小值
    public int getMin() {
        if(!minStack.empty()){
            return minStack.peek();
        }
        return -1;
    }

概念区分

栈、虚拟机栈、栈帧的区别

栈:一种数据结构

虚拟机栈:JVM划分的一块内存

栈帧:调用方法的时候会在虚拟机当中给这块方法开辟一块内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值