算符优先分析求中缀表达的值

算符优先分析求中缀表达的值

1. 定义一个表达式的二义性文法

E → E + E   ∣   E − E   ∣   E ∗ E   ∣   E / E   ∣   ( E )   ∣   i E\rightarrow E+E \, | \, E-E \, | \, E*E \, | \, E/E \, | \, (E) \, | \, i EE+EEEEEE/E(E)i

2. 定义运算规则

对以上表达式的文法按公认的计算顺序规定优先级和结合性如下:

  • 对于运算对象的终结符 i i i,其优先级最高

  • ∗ , / *,/ / 优先级其次,服从左结合。相当于 ∗ > ∗ 、 ∗ > / 、 / > / 、 / > ∗ *>*、*>/、/>/、/>* >>//>//>

  • + , − +,- + 优先级最低,服从左结合。相当于 + > + 、 + > − 、 − > − 、 − > + +>+、+>-、->-、->+ +>++>>>+

  • 对于 “ ( ” , “ ) ” “(”,“)” () 规定括号的优先性大于括号外的运算符,小于括号内的运算符,内括号的优先性大于外括号。

  • 对于句子括号"#"规定与它相邻的任何运算符的优先性都比它大。

3. 算符优先关系表

根据定义的优先规则可以构造出如下优先关系表:

+-*/()i#
+>><<<><>
->><<<><>
*>>>><><>
/>>>><><>
(<<<<<=<
)>>>>>>
i>>>>>>
#<<<<<<=

所给表达式文法虽然是二义性的,但我们人为直观地给出运算符之间的优先关系且这种优先关系是唯一的,有了这个优先关系表我们对与符合文法的表达式的归约过程就是唯一确定的了。

4. 算符优先归约过程

以分析输入串 i 1 + i 2 ∗ i 3 ​ i_1+i_2*i_3​ i1+i2i3为例:

步骤优先关系当前符号剩余输入串移进或归约
(1)#< i 1 i_1 i1+ i 2 i_2 i2* i 3 i_3 i3#移进
(2)# i 1 i_1 i1>+ i 2 i_2 i2* i 3 i_3 i3#归约 E → i E \rightarrow i Ei
(3)#E<+ i 2 i_2 i2* i 3 i_3 i3#移进
(4)#E+< i 2 i_2 i2* i 3 i_3 i3#移进
(5)#E+ i 2 i_2 i2>* i 3 i_3 i3#归约 E → i E \rightarrow i Ei
(6)#E+E<* i 3 i_3 i3#移进
(7)#E+E*< i 3 i_3 i3#移进
(8)#E+E* i 3 i_3 i3>#归约 E → i E \rightarrow i Ei
(9)#E+E*E>#归约 E → E ∗ E E \rightarrow E*E EEE
(10)#E+E>#归约 E → E + E E \rightarrow E+E EE+E
(11)#E=#接受

5. 计算中缀表达式的值

创建两个辅助栈stackOper和stackData,扫描中缀表达式时,用stackOper存储表达式中的运算符,用stackData存储操作数。

stackOper栈顶运算符对应着算符优先关系表第一纵列的运算符,每次从表达式中扫描到的运算符对应着算符优先关系表第一横行的运算符。

基本思想:

  • (1)首先置stackOper和stackData为空栈
  • (2)向stackOper压入运算符#,在表达式后面加上#(作为结束标志)
  • (3)依次扫描表达式中的每一个字符,若是操作数,则将操作数压入stackData中;
    • 若是运算符,则将运算符和stackOper栈顶的运算符比较优先级后做相应操作
      (若栈顶运算符优先级高,则stackData连续弹出两个数,stackOper弹出运算符,按弹出的运算符计算出结果后,将结果压入stackData;否则,直接将新扫描的运算符压入stackOper);
    • 否则,表达式文法错误,抛异常
  • (4)直至整个表达式扫描完毕,stackData栈顶的数值就是运算结果
package IO.util;

import java.util.LinkedList;

/**
 * @author wylu
 * @version 1.0
 * 利用算符优先文法求中缀表达式的值
 */
public class Calculator {

    private static final String EXPRESSION_END_FLAG = "#";
    private static final String WRONG_EXPRESSION = "文法错误";

    private static final char ZERO = '0';
    private static final char NINE = '9';

    /**
     * 栈顶运算符比前扫描到的运算符优先级高
     */
    private static final int PRIORITY_HIGH = 1;
    /**
     * 栈顶运算符与前扫描到的运算符优先级相等
     */
    private static final int PRIORITY_EQUAL = 0;
    /**
     * 栈顶运算符比前扫描到的运算符优先级低
     */
    private static final int PRIORITY_LOW = -1;
    /**
     * 非法运算符
     */
    private static final int OPERATOR_DEDY = 2;


    /**
     * 1)先乘除,后加减
     * 2)同级运算,从左到右依次计算
     * 3)有括号的,先算括号里面的
     *
     * 根据以上三条规则定义如下算符优先关系表:
     *     > : 行位置的运算比列位置的运算的优先级高
     *     < : 行位置的运算比列位置的运算的优先级低
     *     = : 行位置的运算与列位置的运算的优先级相等
     *     $ : 表示这两种运算之间没有可比性,说明输入的式子有文法错误
     */
    private static final String[][] PRIORITY_TABLE = {
            {"$", "+", "-", "*", "/", "(", ")", "#"},
            {"+", ">", ">", "<", "<", "<", ">", ">"},
            {"-", ">", ">", "<", "<", "<", ">", ">"},
            {"*", ">", ">", ">", ">", "<", ">", ">"},
            {"/", ">", ">", ">", ">", "<", ">", ">"},
            {"(", "<", "<", "<", "<", "<", "=", "$"},
            {")", ">", ">", ">", ">", "$", ">", ">"},
            {"#", "<", "<", "<", "<", "<", "$", "="},
    };


    /**
     * 创建两个辅助栈stackOper和stackData,
     * 扫描中缀表达式时,用stackOper存储表达式中的运算符,用stackData存储操作数。
     * stackOper栈顶运算符对应着算符优先关系表第一纵列的运算符,
     * 每次从表达式中扫描到的运算符对应着算符优先关系表第一横行的运算符。
     * 求中缀表达式的基本思想如下:
     * 1) 首先置stackOper和stackData为空栈
     * 2) 向stackOper压入运算符#,在表达式后面加上#(作为结束标志)
     * 3) 依次扫描表达式中的每一个字符,若是操作数,则将操作数压入stackData中;
     *    若是运算符,则将运算符和stackOper栈顶的运算符比较优先级后做相应操作
     *    (若栈顶运算符优先级高,则stackData连续弹出两个数,stackOper弹出运算符,
     *    按弹出的运算符计算出结果后,将结果压入stackData;
     *    否则,直接将新扫描的运算符压入stackOper);
     *    否则,表达式文法错误,抛异常
     * 4) 直至整个表达式扫描完毕,stackData栈顶的数值就是运算结果
     * @param expression 中缀表达式
     * @return 计算结果的字符串形式
     */
    public static String cal(String expression) {

        String express = expression.replaceAll("\\s*", "") + EXPRESSION_END_FLAG;

        LinkedList<String> stackOper = new LinkedList<>();
        LinkedList<Double> stackData = new LinkedList<>();

        stackOper.push(EXPRESSION_END_FLAG);

        char ch;

        for (int i = 0; i < express.length();) {
            ch = express.charAt(i);
            //操作数
            if (ch >= ZERO && ch <= NINE) {
                if (i == express.length() - 2) {
                    stackData.push((double) (ch - ZERO));
                    i++;
                } else {
                    int j = i + 1;
                    while (express.charAt(j) >= ZERO && express.charAt(j) <= NINE) j++;
                    stackData.push(Double.valueOf(express.substring(i, j)));
                    i = j;
                }
            } else { //运算符
                switch (judgePriority(stackOper.peek(), ch)) {
                    case PRIORITY_HIGH:
                        stackData.push(operate(stackData.pop(), stackData.pop(), stackOper.pop()));
                        break;
                    case PRIORITY_EQUAL:
                        stackOper.pop();
                        i++;
                        break;
                    case PRIORITY_LOW:
                        stackOper.push(ch + "");
                        i++;
                        break;
                    default:
                        throw new RuntimeException(new Exception(WRONG_EXPRESSION));
                }
            }
        }

        if (!stackOper.isEmpty()) throw new RuntimeException(new Exception(WRONG_EXPRESSION));

        return String.valueOf(stackData.pop());
    }

    private static Double operate(Double num1, Double num2, String operator) {
        switch (operator) {
            case "+":
                return num2 + num1;
            case "-":
                return num2 - num1;
            case "*":
                return num2 * num1;
            case "/":
                return num2 / num1;
            default:
                return 0.0;
        }
    }

    private static int judgePriority(String stackTopOper, char currentOper) {
        return judgePriority(stackTopOper, currentOper + "");
    }

    /**
     * 比较栈顶运算符和当前扫描的运算符的优先级高级
     * @param stackTopOper 栈顶运算符
     * @param currentOper 当前扫描到的运算符
     * @return 魔数 PRIORITY_HIGH=1 PRIORITY_EQUAL=0 PRIORITY_LOW=-1
     */
    private static int judgePriority(String stackTopOper, String currentOper) {
        if (stackTopOper == null || currentOper == null) return OPERATOR_DEDY;

        int row = 1, col = 1;
        while (row < PRIORITY_TABLE.length
                && !PRIORITY_TABLE[row][0].equals(stackTopOper)) row++;
        while (col < PRIORITY_TABLE[0].length
                && !PRIORITY_TABLE[0][col].equals(currentOper)) col++;

        if (row == PRIORITY_TABLE.length || col == PRIORITY_TABLE[0].length) {
            return OPERATOR_DEDY;
        }

        switch (PRIORITY_TABLE[row][col]) {
            case ">":
                return PRIORITY_HIGH;
            case "<":
                return PRIORITY_LOW;
            case "=":
                return PRIORITY_EQUAL;
            default:
                return OPERATOR_DEDY;
        }
    }

    public static void main(String[] args) {
//        String expression = "2*(3-1)+(5+4)/9";
        String expression = "(5+4)/9+11";
        System.out.println(Calculator.cal(expression));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值