栈应用—前缀、中缀、后缀表达式

中缀表达式

  1. 我们平时所见的表达式都是中缀表达式,如( 3 + 4 )x 5 - 6 、1 + 2 * 4 + 6 / 2
  2. 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)

前缀表达式

  1. 前缀表达式也叫做波兰表达式
  2. 前缀表达式的运算符位于操作数之前,如( 3 + 4 )x 5 - 6对应的中缀表达式是 - x + 3 4 5 6
  3. 前缀表达式计算求值:
    从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
    例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
    3.1 从右至左扫描,将6、5、4、3压入堆栈
    3.2 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),栈顶第一个操作数,次顶元素是第二个操作数,因此计算出3+4的值,得7,再将7入栈
    3.3 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
    3.4 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
  4. 中缀表达式转换成前缀表达式步骤
    (1)初始化两个栈:运算符栈S1和储存中间结果的栈S2;
    (2) 从右至左扫描中缀表达式;
    (3) 遇到操作数时,将其压入S2;
    (4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
    (4-1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
    (4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;
    (4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
    (5) 遇到括号时:
    (5-1) 如果是右括号“)”,则直接压入S1;
    (5-2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;
    (6) 重复步骤(2)至(5),直到表达式的最左边;
    (7) 将S1中剩余的运算符依次弹出并压入S2;
    (8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

后缀表达式

  1. 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
  2. 举例说明:
正常表达式 后缀表达式
( 3 + 4 ) × 5 - 6 3 4 + 5 × 6 –
a + b a b +
a + ( b - c ) a b c - +
a + ( b - c ) * d a b c – d * +
a + d * ( b - c ) a d b c - * +
a = 1 + 3 a 1 3 + =
  1. 后缀表达式的计算求值
    从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
    例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
    (1)从左至右扫描,将3和4压入堆栈;
    (2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
    (3)将5入栈;
    (4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
    (5)将6入栈,最后是-运算符,计算出35-6的值,即29,由此得出最终结果
  2. 中缀表达式转换成后缀表达式步骤
    (1) 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
    (2)从左至右扫描中缀表达式;
    (3)遇到操作数时,将其压s2;
    (4)遇到运算符时,比较其与s1栈顶运算符的优先级:
    (4-1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    (4-2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    (4-3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
    (5)遇到括号时:
    (5-1)如果是左括号“(”,则直接压入s1
    (5-2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    (6)重复步骤2至5,直到表达式的最右边
    (7)将s1中剩余的运算符依次弹出并压入s2
    (8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
  3. 中缀转后缀表达式举例:将“1+((2+3)×4)-5”转为后缀表达式
扫描到的元素 s2(栈底->栈顶) s1 (栈底->栈顶) 说明
1 1 数字,直接入栈
+ 1 + s1为空,运算符直接入栈
( 1 + ( 左括号,直接入栈
( 1 + ( ( 同上
2 1 2 + ( ( 数字
+ 1 2 + ( ( + s1栈顶为左括号,运算符直接入栈
3 1 2 3 + ( ( + 数字
) 1 2 3 + + ( 右括号,弹出运算符直至遇到左括号
x 1 2 3 + + ( × s1栈顶为左括号,运算符直接入栈
4 1 2 3 + 4 + ( × 数字
) 1 2 3 + 4 x + 右括号,弹出运算符直至遇到左括号
- 1 2 3 + 4 x + - -与+优先级相同,因此弹出+,再压入-
5 1 2 3 + 4 × + 5 - 数字
到达最有端 1 2 3 + 4 × + 5 - s1中剩余的运算符

代码实现

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Stack;

/**
 * 后缀(逆波兰)表达式计算器
 *      支持运算符号: + - * / ( )
 *      支持整数和浮点数
 * @author 陈治远
 * @create 2019-07-29 18:25
 */
public class SuffixCalculator {
    public static void main(String[] args) {
        System.out.println("直接计算(-23.6 + 10) * (-10 / 2.0)的结果为:" + (-23.6 + 10) * (-10 / 2.0));
        String suffixExperssion = infixToSuffix("(-23.6 + 10) * (-10 / 2.0)");
        System.out.println("(-23.6 + 10) * (-10 / 2.0)的后缀表达式为:" + suffixExperssion);
        System.out.println("计算结果为:" + calculate(suffixExperssion));
    }
    
    /**
     * 辅助方法:获得符号的优先级
     */
    private static Integer getPriority(String str) {
        switch (str) {
            case "+":
            case "-":
                return 1;
            case "*":
            case "/":
                return 2;
            default:
                throw new RuntimeException("操作符不合法:" + str + ",操作符只应该包括+ - * /");
        }
    }

    /**
     * 辅助方法:判断一个字符串是否是数字
     */
    private static boolean isNumer(String str) {
        try {
            Double.parseDouble(str);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 辅助方法:根据操作符oper计算出对应的值
     */
    private static double calcuate(double num1, double num2, String oper) {
        double res = 0.0;
        switch (oper) {
            case "+":
                res = num1 + num2;
                break;
            case "-":
                res = num1 - num2;
                break;
            case "*":
                res = num1 * num2;
                break;
            case "/":
                res = num1 / num2;
                break;
            default:
                throw new RuntimeException("操作符不正确:" + oper + ",操作符只应该包括+ - * /");
        }
        return res;
    }

    /**
     * 将中缀表达式转后缀表达式
     * @return 返回后缀表达式
     */
    public static String infixToSuffix(String infixExperssion) {
        Matcher matcher = Pattern.compile("-?\\d+(\\.\\d+)?|\\(|\\)|\\+|\\-|\\*|\\/").matcher(infixExperssion);
        Stack s1 = new Stack<>();
        Stack s2 = new Stack<>();

        while (matcher.find()) {
            String str = matcher.group();

            if (isNumer(str)) {
                // 如果是数字,直接压入s2
                s2.push(str);
            } else {
                // 不是数字

                // 如果s1为空,直接压入s1,然后退出当次循环
                if (s1.isEmpty()) {
                    s1.push(str);
                    continue;
                }

                if ("(".equals(str)) {
                    // 如果是左括号,直接压入左括号,然后退出当次循环
                    s1.push(str);
                    continue;
                } else if (")".equals(str)) {
                    // 如果是右括号,就弹出s1栈顶符号,压入s2,直到s1栈顶是左括号
                    while (!"(".equals(s1.peek())) {
                        s2.push(s1.pop());
                    }
                    // 弹出左括号并丢弃,这样就消除了一对括号
                    s1.pop();
                    continue;
                }

                // 如果s1不为空
                // 1.如果s1栈顶是左括号,直接进s1栈,不用判断优先级
                // 2.如果s1栈顶不是左括号,那么就要比较优先级,如果优先级大于栈顶符号,也直接进s1栈
                if ("(".equals(s1.peek()) || getPriority(str) > getPriority(s1.peek())) {
                    s1.push(str);
                    continue;
                }

                // 走到这一步,说明上面所有情况都排除了
                // 现在这种情况就是str优先级小于等于s1栈顶符号
                // 此时就弹出s1栈顶符号,压入s2,,直到s1栈顶符号的优先级大于str的优先级 或
                while (getPriority(str) <= getPriority(s1.peek())) {
                    String top = s1.pop();
                    s2.push(top);
                    // s1每次弹出栈顶后,都判断s1是否为空 或者 s1栈顶为左括号
                    // 如果是就跳出循环
                    if (s1.isEmpty() || s1.peek().equals("(")) {
                        break;
                    }
                }
                // 上面while循环只是将优先级>=str的符号弹出并压入s2,没有str的进栈操作
                // 循环结束后就说明str具备了进s1栈的资格,这里将str压入s1
                s1.push(str);
            }
        }

        // 当中缀表达式扫描完毕,s1中可能还有剩余的符号,应该把它们弹出并压入s2,知道s1为空
        while (!s1.isEmpty()) {
            String top = s1.pop();
            s2.push(top);
        }

        // 拼接后缀表达式
        StringBuilder sb = new StringBuilder();
        while (!s2.isEmpty()) {
            sb.insert(0, s2.pop() + " ");
        }
        // 去掉最后一个空格
        CharSequence suffixExperssion = sb.subSequence(0, sb.lastIndexOf(" "));
        return suffixExperssion.toString();
    }

    /**
     * 计算后缀表达式的值
     * @param suffixExperssion 后缀表达式(逆波兰表达式)
     * @return
     */
    public static double calculate(String suffixExperssion) {
        // 后缀表达式中操作数和运算符之间按空格分隔,所以正则表达式匹配所有非空格字符
        // 也可以使用String对象的split(regex)得到一个数组,再遍历数组
        Matcher matcher = Pattern.compile("\\S+").matcher(suffixExperssion);
        // 创建数字栈和符号栈,使用的java.util.Stack
        Stack numStack = new Stack<>();
        while (matcher.find()) {
            String str = matcher.group();
            // 如果子串str是数字,将数字压入numStack
            if (isNumer(str)) {
                numStack.push(Double.parseDouble(str));
            } else {
                // 如果子串str是符号,直接在numStack弹两个数然后将计算结果压栈进行计算,然后将结果入栈即可
                double num1 = numStack.pop();
                double num2 = numStack.pop();
                double res = calcuate(num2, num1, str);
                // 将计算结果压栈
                numStack.push(res);
            }
        }
        // 计算完毕后numStack中的数据就是后缀表达式的值
        return numStack.pop();
    }
}

控制台打印

直接计算(-23.6 + 10) * (-10 / 2.0)的结果为:68.0
(-23.6 + 10) * (-10 / 2.0)的后缀表达式为:-23.6 10 + -10 2.0 / *
计算结果为:68.0

你可能感兴趣的:(栈应用—前缀、中缀、后缀表达式)