栈的应用-表达式求值

栈是一种先进后出的数据结构,栈的应用很多,表达式求值问题就是一个典型的应用,包含:括号匹配,中缀/后缀表达式的转换以及后缀表达式求值…


括号匹配

使用栈,可以检查一个表达式的括号是否匹配,由于只关心括号的成对匹配,而不关心括号的类别,所以假设只包含()。

检查括号匹配的算法思路是:

① 依次遍历表达式,记录当前字符
② 如果当前字符不是括号,继续遍历下一个字符
③ 如果当前字符是左括号,入栈
④ 如果单曲字符是右括号,出栈;如果出栈元素是左括号,表示匹配一对括号,如果出栈元素是右括号或者此时栈空,括号不匹配。
⑤ 当表达式遍历完成后,如果栈为空,表示括号匹配,否则不匹配。

public static boolean isValidExpression(String expression){
    Stack<String> stack=new SeqStack<>();
    int exLen=expression.length();
    for(int i=0;i<exLen;i++){
        char curChar=expression.charAt(i);
        switch (curChar){ 
            case '(':  //左括号,入栈
                stack.push(curChar+"");
                break;
            case ')':  //右括号出栈
                if(stack.isEmpty()||!"(".equals(stack.pop())){
                    return false;  //如果栈空或者出栈右括号,不匹配
                }
                break;
        }
    }
    if(!stack.isEmpty()){  //如果栈不为空,不匹配
        return false;
    }
    return true;
}

中缀表达式转为后缀表达式

表达式可以直接求值运算,平时所写表达式叫做中缀表达式;使用中缀表达式计算必须使用两个栈,一个用来记录操作数,一个用来记录括号。并且中缀表达式会根据括号的优先级导致无法从左向右顺序计算,操作比较繁琐。

后缀表达式:将运算符置于操作数之后的表达式;在后缀表达式中不再有括号。

比如中缀表达式:1+2*(3-4)+5,转为后缀表达式的过程如下图所示:

实现的具体算法如下:
① 遍历中缀表达式,记当前字符为curChar
② 如果curChar是数字,添加到后缀表达式
③ 如果curChar是运算符,将curChar与栈顶运算符比较,如果栈顶运算符优先级不低于curChar的优先级,则出栈加入到后缀表达式,直到栈顶运算符优先级低于curChar。
最后将curChar入栈
④ 如果curChar是左括号,入栈(左括号在栈中的优先级最低)
⑤ 如果curChar是右括号,出栈加入后缀表达式,直到出栈的是左括号
⑥ 表达式遍历完毕后,将栈中剩余的运算符全部依次出栈,加入后缀表达式

为了方便比较各个运算符的优先级,可以准备好一个HashMap存储各运算符的优先级:

public static Map<String, Integer> getOperatorPriority() {
    Map<String, Integer> operatorPriority = new HashMap<>();
    operatorPriority.put("(", 0);
    operatorPriority.put("+", 1);
    operatorPriority.put("-", 1);
    operatorPriority.put("*", 2);
    operatorPriority.put("/", 2);
    operatorPriority.put("^", 3);
    return operatorPriority;
}

代码实现如下:

public static StringBuilder toPostfix(String infix) {
    StringBuilder postfix = new StringBuilder();
    Stack<String> stack = new SeqStack<>();
    Map<String, Integer> operatorPriority = Expression.getOperatorPriority();
    int infixLen = infix.length();
    for (int i = 0; i < infixLen;) {
        char curChar = infix.charAt(i);
        switch (curChar) {
            case '(':
                stack.push(curChar + "");
                i++;
                break;
            case ')':
                String top = stack.pop();
                while (top != null && !"(".equals(top)) {
                    postfix.append(top);
                    top = stack.pop();
                }
                i++;
                break;
            case '+':case '-': case '*':case '/':case '^':
                while (!stack.isEmpty() && operatorPriority.get(stack.peek()) >= operatorPriority.get(curChar+"")) {
                    postfix.append(stack.pop());
                }
                stack.push(curChar + "");
                i++;
                break;
            default: //考虑到多位数字的情况,需要使用一个循环来读取连续的数字字符,并以空格分隔
                while (i<infixLen&&(curChar <= '9' && curChar >= '0')) {
                    postfix.append(curChar + "");
                    i++;
                    if(i<infixLen)     curChar = infix.charAt(i);
                }
                postfix.append(" ");
        }
    }
    while (!stack.isEmpty()) {
        postfix.append(stack.pop());
    }
    return postfix;
}

有一个小细节需要注意,一般建议在循环中对字符串的添加操作使用StringBuilder/StringBuffer,效率会高一些。

后缀表达式求值

终于到了最重要的一步了。

后缀表达式的求值是一种顺序求值,借助一个数字栈,进行操作。

具体算法如下:
① 遍历后缀表达式,记录当前字符为curChar
② 如果curChar为数字,入栈
③ 如果curChar为运算符,则出栈两个数,进行运算,运算结果再入栈

需要注意的是: 由于可能出现123这样的多位数字,而存储是以字符为单位的,所以如果curChar是数字的话,需要将后面的数字字符先转为一个完整的数字,再进行操作(这时候就需要借助上面提到的使用空格分隔数字的技巧了)

代码如下:

public static Double toValue(StringBuilder postfix) {
    int postLen=postfix.length();
    Double result=0.0;
    Stack<Double> numStack=new SeqStack<>();
    for(int i=0;i<postLen;i++){
        char curChar=postfix.charAt(i);
        if(curChar>='0'&&curChar<='9'){
            result=0.0;
            while (curChar!=' '){
                result=result*10+curChar-'0';
                curChar=postfix.charAt(++i);
            }
            numStack.push(result);
        }
        else{
            if(curChar!=' '){
                double y=numStack.pop();
                double x=numStack.pop();
                switch (curChar){
                    case '+':
                        result=x+y;
                        break;
                    case '-':
                        result=x-y;
                        break;
                    case '*':
                        result=x*y;
                        break;
                    case '/':
                        result=x/y;
                        break;
                    case '^':
                        result=Math.pow(x,y);
                        break;
                }
                numStack.push(result);
            }
        }
    }
    return result;
}

这样的操作其实不算完美,实际的表达式还可以是浮点数,或者包含更多的括号以及运算符。

就需要进一步的完善了。



微信公众号:SmartPig
个人博客:http://smartpig612.club

在手机计算机应用中进行表达式求值,通常会采用这种数据结构来处理,因为在计算机中,由于不同的运算符具有不同的优先级,又要考虑括号,算术表达式求值不可能严格地从左到右进行,借助可以有效解决这一问题 [^1]。 常见的方法有双法来处理中缀表达式以及使用实现前缀表达式的计算。 ### 双法处理中缀表达式 设两个,`n` 和 `o`,分别存储操作数和操作符。从左到右扫描中缀表达式- 遇到一个操作数,将其压入 `n` 中。 - 遇到操作符(第一个直接入 `o`): - 若 `o` 顶元素优先级小于当前扫描操作符,将当前操作符压入 `o`。 - 若 `o` 顶元素优先级大于等于当前扫描操作符,取 `o` 顶操作符和 `n` 两个元素,计算得到结果再压入 `n`,循环直到 `o` 顶元素优先级小于当前操作符,再将当前操作符压入 `o`。 遍历完表达式后,要把 `o` 中操作符全部计算完,此时 `n` 的顶元素即为计算结果 [^2]。 以下是简单的 Python 代码示例: ```python def apply_operator(operators, values): operator = operators.pop() right_operand = values.pop() left_operand = values.pop() if operator == '+': values.append(left_operand + right_operand) elif operator == '-': values.append(left_operand - right_operand) elif operator == '*': values.append(left_operand * right_operand) elif operator == '/': values.append(left_operand / right_operand) def precedence(operator): if operator in ('+', '-'): return 1 if operator in ('*', '/'): return 2 return 0 def evaluate(expression): values = [] operators = [] i = 0 while i < len(expression): if expression[i].isdigit(): num_str = "" while i < len(expression) and (expression[i].isdigit() or expression[i] == '.'): num_str += expression[i] i += 1 values.append(float(num_str)) i -= 1 elif expression[i] in ('+', '-', '*', '/'): while (operators and precedence(operators[-1]) >= precedence(expression[i])): apply_operator(operators, values) operators.append(expression[i]) i += 1 while operators: apply_operator(operators, values) return values[-1] expression = "3+5*2" result = evaluate(expression) print(result) ``` ### 前缀表达式的计算 在中缀表达式转前缀表达式的过程中,从右向左扫描表达式,只要右边的运算符能先计算,就优先计算右边的。使用实现前缀表达式的计算(先出的是左操作数): - 从右往左扫描下一个元素,直到处理完所有的元素。 - 若扫描到操作数压入,并回到上一步;若扫描到运算符,则弹出两个顶元素,执行相应的运算,将结果压回顶,并回到第一步。 - 往复循环上述步骤,直到处理完所有的元素,中必将只有一个元素,否则前缀表达式有误,是不合法的 [^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值