1. 栈的概念及实现

栈是一种先进后出的数据结构,先进后出是指最先入栈的数据最后出栈最后入栈的数据最先出栈, 如下图

栈的实现:

https://github.com/znxcmakhsd/DS/tree/main/12-18/MyStack

这个栈的实现底层用数组存储数据

栈也可以用单链表或者是双向链表实现

如果用单链表实现栈,栈的入栈和出栈操作必须是头插/头删, 原因入下

就算给单链表结构加个指向最后节点的引用 尾插可以实现入栈,但是尾删不能实现出栈。

因为尾删需要找到尾节点的前一个, last引用指向尾节点 没办法找到尾节点前面,只能遍历链表

 

如果用双向链表实现,则头插头删 / 尾插尾删都可以实现入栈和出栈操作

因为双向链表有两个引用指向头和尾, 且无论在头还是在尾插入删除数据都只需要修改指向 不需要移动数据,复杂度为O(1)

关于栈的总结:

栈是一个特殊的数据结构,它底层可以用数组或者链表存储数据,但是它组织操作数据方式必须是后进先出

2. 栈的应用

计算后缀表达式结果

什么是后缀表达式 ?

先来看什么是中缀表达式:

( (2 + 1) * 3 )

什么是后缀表达式? 如何将中缀表达式转换为 后缀表达式?

根据运算顺序添加括号,然后将中缀表达式中的运算符移到括号外面,然后去掉所有的括号

 ( (2 + 1) * 3 )   ——运算符移到括号外面 ——》 ( ( 2 1 ) + 3 ) *  ——去掉所有的括号——》 2 1 + 3 这个表达式就是一个后缀表达式

 

再来看一个更复杂的例子:

中缀表达式: a + b * c + (d * e + f) * g

先根据运算顺序添加括号: ( (a + (b * c)1 )2 + ( (d * e)3 + f)4 * g)5 )6  注意: 数字表示添加括号的顺序

将运算符移到括号外面:( (a (b c)*1 )+2 ( (de)*3 f)+4 g)*5 )+6

去掉所有的括号: a b c *+ de* f+ g* +

知道了什么是后缀表达式 中缀表达式如何转为后缀表达式后, 再来看如何使用栈计算后缀表达式结果 

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (int i = 0;i < tokens.length;i++) {
            String str = tokens[i];
            if (!isOperator(str)) {
                // 如果是数字入栈
                int num = Integer.parseInt(str);
                stack.push(num);                
            }else {
                // 是运算符, 出栈计算                
                int right = stack.pop();
                int left = stack.pop();
                switch (str) {
                    case "+":
                        stack.push(left + right);
                        break;
                    case "-":
                        stack.push(left - right);
                        break;
                    case "*":
                        stack.push(left * right);
                        break;
                    case "/":
                        stack.push(left / right);
                        break;    
                }
            }
        }
        return stack.peek();
    }
    private boolean isOperator(String str) {
        if (str.equals("+") || str.equals("-") 
            || str.equals("*") || str.equals("/")) {
                return true;
            }
            return false;
    }
}

括号匹配

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 {
                // 不匹配情况1: 栈里没有左括号 -> )
                if (stack.empty()) {
                    return false;
                }
                // 左括号与右括号匹配,如果匹配 将左括号出栈准备下一次匹配
                char top = stack.peek();
                if (top == '(' && ch == ')' || top == '[' && ch == ']' || top == '{' && ch == '}') {
                    stack.pop();
                }else {
                    // 不匹配情况2: 左括号与右括号不匹配 -> (]
                    return false;
                }
            }
        }
        // 走到这里表示遍历完了字符串,有两种可能结果
        // 不匹配情况3: 栈里还有左括号 -> (()
        if (!stack.empty()) {
            return false;
        }
        // 遍历完了字符串 同时栈为空, 证明所有括号匹配
        return true;
    }
}

 

判断正确出栈顺序

什么是正确的出栈顺序 ?

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

A: 1,4,3,2  B: 2,3,4,1   C: 3,1,4,2   D: 3,4,2,1

A: 1入 1出 2入 3入 4入 4出 3出 2出  -> 正确  

B:  1入 2入 2出 3入 3出 4入4出 1出  -> 正确  

C: 1入 2入 3入 3出  —> 错误,因为只有先出2才能出1

D: 1入 2入 3入 3出 4入 4出 2出 1出  -> 正确  

这题的解题思路 建议画图走读代码进行理解:

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param pushV int整型一维数组
     * @param popV int整型一维数组
     * @return bool布尔型
     */
   public static boolean IsPopOrder (int[] pushV, int[] popV) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0;i < pushV.length;i++) {
            stack.push(pushV[i]);
            while (!stack.empty() && j < popV.length && stack.peek() == popV[j]) {
                stack.pop();
                j++;
            }
        }
        // 最后有两种判断出栈顺序
        // 1. 栈是否为空
        /*if (stack.empty()) {
            return true;
        }else {
            return false;
        }*/

        // 2. j == popV.length
        return j == popV.length;
    }
}

最小栈

所以最小栈需要用两个栈实现

关于最小栈入栈出栈具体的实现思路,建议画图走读下面代码

package MinStack;

import java.util.Stack;

class MinStack {

    public Stack<Integer> stack = new Stack<>();
    public Stack<Integer> minStack = new Stack<>();

    public MinStack() {

    }

    public void push(int val) {
        stack.push(val);
        // 如果最小栈为空, 则直接入栈
        if (minStack.empty()) {
            minStack.push(val);
        }else {
            // 如果最小栈不为空, 比较最小栈栈顶元素与val
            // 如果val小于或者等于栈顶元素 入栈
            if (val < minStack.peek()) {
                minStack.push(val);
            }
        }
    }

    // 删除栈顶元素
    public void pop() {
        // 普通栈肯定是要出栈
        // 如果普通栈出栈的元素 与 最小栈栈顶元素一样
        // 则最小栈出栈, 因为出栈的会是最小元素
        int top = stack.pop();
        if (top == minStack.peek()) {
            minStack.pop();
        }
    }

    public int top() {
        return stack.peek();
    }

    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();
 */

 

posted @ 2023-12-19 19:53  qyx1  阅读(91)  评论(0)    收藏  举报