算术表达式求值的实现(整数运算)

数学表达式求值实现详解

前言 :
表达式求值的过程
1.中缀表达式转为后缀表达式
在计算机中,中缀表达式转后缀表达式时需要借助一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右依次扫描中缀表达式中的每一项,具体转化过程如下:
1)遇到操作数。直接加入后缀表达式。
2)遇到界限符。若为“(”,则直接入栈;若为“)”,则不入栈,且依次弹出栈中的运算符并加入后缀表达式,直到遇到“(”为止,并直接删除“(”。
3)遇到运算符。① 若其优先级高于栈顶运算符或遇到栈顶为“(”,则直接入栈;
			② 若其优先级低于或等于栈顶运算符,则依次弹出栈中的运算符并加入后缀表达式,
				直到遇到一个优先级低于它的运算符或遇到“(”或栈空为止,之后将当前运算符入栈。
按上述方法扫描所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。

例如,中缀表达式 A+B*(C-D)-E/F 转后缀表达式的过程如下表所示。

步骤 待处理序列 栈内 后缀表达式 扫描项 说明
1 A+B*(C-D)-E/F - - A A 加入后缀表达式
2 +B*(C-D)-E/F + A + + 入栈
3 B*(C-D)-E/F + A B B 加入后缀表达式
4 *(C-D)-E/F +* AB * * 优先级高于栈顶 +* 入栈
5 (C-D)-E/F +*( AB ( ( 直接入栈
6 C-D)-E/F +*( AB C C 加入后缀表达式
7 -D)-E/F +*(- ABC - 栈顶为 (- 直接入栈
8 D)-E/F +*(- ABC D D 加入后缀表达式
9 )-E/F +*(- ABCD ) 遇到 ),弹出 - 并删除 (
10 -E/F +* ABCD- - - 优先级低于栈顶 *,依次弹出 *+- 入栈
11 E/F - ABCD-+ E E 加入后缀表达式
12 /F -/ ABCD-+E / / 优先级高于栈顶 -/ 入栈
13 F -/ ABCD-+E F F 加入后缀表达式
14 -/ ABCD-+EF - 字符扫描完毕,弹出剩余运算符
- ABCD-+EF/- - 结束

2.后缀表达式求值

通过后缀表示计算表达式值的过程:从左往右依次扫描表达式的每一项,
	若该项是操作数,则将其压入栈中;
	若该项是操作符,则从栈中退出两个操作数Y和X,形成运算指令XY,并将计算结果压入栈中。当所有项都扫描并处理完后,顶存放的就是最后的计算结果

例如,后缀表达式ABCD-+EF/-的求值过程如下表所示。

扫描项 项类型 动作 栈中内容
1 - - 置空栈
2 A 操作数 入栈 A
3 B 操作数 入栈 A B
4 C 操作数 入栈 A B C
5 D 操作数 入栈 A B C D
6 - 操作符 D、C出栈,计算C-D,结果R₁入栈 A B R₁
7 * 操作符 R₁、B出栈,计算B×R₁,结果R₂入栈 A R₂
8 + 操作符 R₂、A出栈,计算A+R₂,结果R₃入栈 R₃
9 E 操作数 入栈 R₃ E
10 F 操作数 入栈 R₃ E F
11 / 操作符 F、E出栈,计算E/F,结果R₄入栈 R₃ R₄
12 - 操作符 R₄、R₃出栈,计算R₃-R₄,结果R₅入栈 R₅

具体实现:

一、核心数据结构设计
// 运算符集合(四则运算)
private static final Set<Character> operators = Set.of('+', '-', '*', '/');

// 数字字符集合(0-9)
private static final Set<Character> numbers = Set.of('1','2','3','4','5','6','7','8','9','0');

// 运算符优先级矩阵(行/列顺序:+ - * /)
private static final int[][] prior = {
    {0, 0, -1, -1},  // + 
    {0, 0, -1, -1},  // -
    {1, 1, 0, 0},    // *
    {1, 1, 0, 0}     // /
};

// 运算符到矩阵索引的映射
private static final Map<Character, Integer> map = Map.of(
    '+', 0, '-', 1, '*', 2, '/', 3
);

关键要点:

  • 优先级矩阵中 1 表示行运算符优先级 > 列运算符,-1 表示 <
  • */优先级高于+-(符合数学规则)
  • 相同优先级运算符从左到右计算(矩阵对角线为0)

二、中缀转后缀表达式算法
Stack<Character> stack = new Stack<>();
for (int i = 0; i < midExpression.length(); i++) {
    char item = midExpression.charAt(i);
    
    // 1. 处理数字(多位数拼接)
    if (numbers.contains(item)) {
        StringBuilder temp = new StringBuilder();
        while (i < midExpression.length() && numbers.contains(midExpression.charAt(i))) {
            temp.append(midExpression.charAt(i));
            i++;
        }
        i--; // 回退指针
        // 将整数转换为char存储(限制:0-65535)
        postExpression.append((char) Integer.parseInt(temp.toString()));
    }
    
    // 2. 处理括号
    else if (item == '(') {
        stack.push(item);
    } else if (item == ')') {
        while (stack.peek() != '(') {
            postExpression.append(stack.pop());
        }
        stack.pop(); // 弹出'('
    }
    
    // 3. 处理运算符
    else {
        // 优先级比较核心逻辑
        if (stack.empty() || stack.peek() == '(') {
            stack.push(item);
        } else {
            int m = map.get(item);     // 当前运算符
            int n = map.get(stack.peek()); // 栈顶运算符
            
            if (prior[m][n] == 1) { // 当前>栈顶
                stack.push(item);
            } else { // 当前<=栈顶
                postExpression.append(stack.pop());
                // 继续比较新栈顶
                while (!stack.empty() && stack.peek() != '(' 
                       && prior[map.get(stack.peek())][m] != -1) {
                    postExpression.append(stack.pop());
                }
                stack.push(item);
            }
        }
    }
}
// 弹出剩余运算符
while (!stack.empty()) {
    postExpression.append(stack.pop());
}

转换规则详解:

  1. 数字处理:连续扫描数字字符拼接成完整整数

    • 示例:"123" → 拼接后转为char(123)
    • 注意:char范围限制(0-65535),实际应用需优化
  2. 括号处理

    • ( 直接入栈
    • ) 弹出栈中元素直到遇到(
  3. 运算符优先级处理

    • 当前运算符 > 栈顶:入栈
    • 当前运算符 ≤ 栈顶:弹出栈顶后重新比较
    • 栈顶为(时直接入栈

三、后缀表达式求值算法
Stack<BigDecimal> operand = new Stack<>();
for (int i = 0; i < postExpression.length(); i++) {
    char c = postExpression.charAt(i);
    
    if (!operators.contains(c)) {
        // char转回原始整数值
        operand.push(BigDecimal.valueOf((int) c));
    } else {
        BigDecimal a = operand.pop();
        BigDecimal b = operand.pop();
        
        switch (c) {
            case '+' -> operand.push(a.add(b));
            case '-' -> operand.push(b.subtract(a)); // 注意顺序:b-a
            case '*' -> operand.push(a.multiply(b));
            case '/' -> {
                try {
                    // 除法设置精度(3位小数+四舍五入)
                    operand.push(b.divide(a, 3, BigDecimal.ROUND_HALF_UP));
                } catch (ArithmeticException e) {
                    System.err.println("计算错误: " + e.getMessage());
                    System.exit(1);
                }
            }
        }
    }
}
// 最终结果
System.out.println(operand.pop());

求值关键点:

  1. 运算数栈存储BigDecimal保证精度
  2. 运算符操作顺序:
    • 弹出栈顶 → 第二个操作数(a)
    • 次栈顶 → 第一个操作数(b)
    • 特别注意减法/除法顺序b - ab / a
  3. 除法异常处理:
    • 捕获ArithmeticException(如除零错误)
    • 设置精度和舍入模式(3位小数+四舍五入)

四、示例执行流程

以表达式 "12+13*(14-15)-16/17" 为例:

步骤1:中缀转后缀

原始中缀:12 + 13 * (14 - 15) - 16 / 17
转换过程:
  数字12 → char(12)
  '+'入栈
  数字13 → char(13)
  '*'优先级>栈顶'+' → 入栈
  '('入栈
  数字14 → char(14)
  '-'入栈
  数字15 → char(15)
  遇到')' → 弹出'-' → 弹出'('
  '-'优先级<=栈顶'*' → 弹出'*' → 弹出'+' → 压入'-'
  数字16 → char(16)
  '/'入栈
  数字17 → char(17)
最终后缀:12 13 14 15 - * + 16 17 / - 
(注:此处数字为char值示意)

步骤2:后缀求值

操作栈过程:
  压入12,13,14,15
  遇到- → 15-14 → -1
  遇到* → 13*(-1) → -13
  遇到+ → 12+(-13) → -1
  压入16,17
  遇到/ → 16/17 → -0.94118 → 保留3位小数 → -0.941
  遇到- → -1 - 0.941 → -1.941
最终结果:-1.941

五、完整代码(带有详细注释Java代码)
import java.math.BigDecimal;
import java.util.*;

public class ExpressionEvaluation {

    private static final Set<Character> operators = Set.of('+', '-', '*', '/'); // 定义运算符集合

    private static final Set<Character> numbers = Set.of('1', '2', '3', '4', '5', '6', '7', '8', '9', '0'); // 定义运算数基数集合

    private static final int[][] prior = { // 运算符优先级定义矩阵 横纵依次为 + - * /  ,每个人位置定义为如果横运算符优先级大于纵,则填为1 小于为-1 相等为0
            {0, 0, -1, -1},
            {0, 0, -1, -1},
            {1, 1, 0, 0},
            {1, 1, 0, 0}
    };

    private static final Map<Character, Integer> map = Map.of( // 定义运算符映射关系,+ - * / 依次映射成0 1 2 3以方便优先级矩阵的使用
            '+', 0,
            '-', 1,
            '*', 2,
            '/', 3
    );


    public static void main(String[] args) {
        StringBuilder midExpression = new StringBuilder("12+13*(14-15)-16/17"); // 原始数学计算表达式 (即中缀表达式,以"12+13*(14-15)-16/17"为例),
        StringBuilder postExpression = new StringBuilder(); // 后缀表达式(将表达式中运算数转化为char类型后的后缀表达式)
        // 第一步: 将数学表达式转化为后缀表达式
        Stack<Character> stack = new Stack<>(); // 转后缀表达式所用的栈
        for (int i = 0; i < midExpression.length(); i++) { // 依次扫描中缀表达式中的每一项
            char item = midExpression.charAt(i);
            if (numbers.contains(item)) { // 1. 当前项item是运算数,直接加入后缀表达式
                // 1.1 可知我们扫描的是单个字符,要通过手段找到与其关联的所有字符来组成对应的整数
                // 手段: 记录已扫描的字符,并且继续向后扫描直到下一个字符不再是整数或者表达式扫描完即可(也即不包含在numbers集合中)
                StringBuilder temp = new StringBuilder();
                while (i < midExpression.length() && numbers.contains(midExpression.charAt(i))) { // 循环结束后所得temp即为完整整数的字符串表达
                    temp.append(midExpression.charAt(i));
                    i++;
                }
                i--; // 由于此时i指向的是中缀表达式的下一个字符,而该if代码段执行完后又要加一,这样就会导致跳过了一个扫描字符,所以我们再在此先减一

                /*
                此处我们将转化后的整数字符串temp转化为整数Integer.parseInt()后又使用(char)强制类型转换将其转化为字符后才存入后缀表达式
                这是为了方便后续的后缀表达式求值,而整数int到char类型的转化是有限制的:
                    char类型在 Java 中是无符号的 16 位整数,其取值范围是0到65535(即\u0000到\uffff)。
                    从数值角度看,任意处于0到65535之间的整数,都能强制转换为char类型。
                    从字符编码方面来说,这个范围覆盖了 Unicode 的基本多文种平面(BMP),其中包含了大多数常见的字符,像 ASCII 字符、中文、日文、韩文等
                 */
                postExpression.append((char) Integer.parseInt(String.valueOf(temp))); // 1.2 将其转为整数后加入后缀表达式

            } else if (!operators.contains(item)) { // 2. 当前项不是运算数也不是运算符,也即其为界限符 '(' , ')'时
                // 2.1 若其为'(' 直接入栈
                if (item == '(')
                    stack.push(item);
                    // 2.2 若其为')' 则不入栈,且依次弹出栈中运算符并加入后缀表达式,直到遇到'('为止,并弹出'('
                else {
                    while (stack.peek() != '(') { // 不为左括号则一直弹出
                        postExpression.append(stack.pop());
                    }
                    stack.pop(); // 删除左括号
                }
            } else { // 3.当前项为运算符
                // 3.1 若 栈空 或 其优先级大于栈顶运算符 或 栈顶为'(',直接入栈
                if (stack.empty()) stack.push(item);
                else if (operators.contains(stack.peek())) { // 栈顶为运算符
                    // 先获取栈顶元素和当前项对应坐标,通过优先级矩阵判断优先级
                    int m = map.get(item);
                    int n = map.get(stack.peek());
                    if (prior[m][n] == 1) { // 当前项优先级大于栈顶元素
                        stack.push(item); // 直接入栈
                    } else { // 3.2 若其由优先级低于或等于栈顶运算符,则依次弹出栈顶元素加入后缀表达式直到遇到一个优先级低于当前项item的栈顶元素 或 栈顶元素为'(' 或 栈空
                        postExpression.append(stack.pop()); // 先弹出当前优先级低当前扫描项的栈顶元素到后缀表达式
                        if (stack.peek() == '(') { // 遇到'(' 直接入栈
                            stack.push(item);
                            break;
                        } else if (stack.empty()) { // 栈空,直接入栈
                            stack.push(item);
                            break;
                        } else {
                            n = map.get(stack.peek());
                            while (!stack.empty() && !(prior[n][m] == -1)) { // 继续扫描直到当前item优先级大于栈顶元素(也即上述优先级低于当前项item的栈顶元素)为止
                                postExpression.append(stack.pop());
                            }
                            stack.push(item);
                        }
                    }
                } else // 栈顶只能为运算符或'('
                    stack.push(item);
            }
        }
        // 中缀表达式扫描完毕,则将栈中元素全部弹出到后缀表达式
        while (!stack.empty()) {
            postExpression.append(stack.pop());
        }

        // 第二步: 对后缀表达式求值
        Stack<BigDecimal> operand = new Stack<>(); // 运算数栈
        for (int i = 0; i < postExpression.length(); i++) {
            /*
                依次扫描后缀表达式,
                    遇到运算数时入运算数栈
                    遇到运算符op时从运算数栈operand中依次取两个数 A B 做   B op A的运算,将结果压入运算数栈operand中
             */
            if (!operators.contains(postExpression.charAt(i))) {
                operand.push(BigDecimal.valueOf((int) postExpression.charAt(i)));
            } else {
                BigDecimal a = operand.pop();
                BigDecimal b = operand.pop();
                switch (postExpression.charAt(i)) {
                    case '+' -> operand.push(a.add(b));
                    case '-' -> operand.push(b.subtract(a));
                    case '*' -> operand.push(a.multiply(b));
                    case '/' -> {
                        try {
                            // 直接在除法时设置精度和舍入模式,当前为两位有效数字和四舍五入
                            BigDecimal result = b.divide(a, 3, BigDecimal.ROUND_HALF_UP);
                            operand.push(result);
                        } catch (ArithmeticException e) {
                            // 处理除以零或无限循环小数的情况
                            System.err.println("计算错误: " + e.getMessage());
                            // 可以选择推回默认值或进行其他处理
                            //operand.push(BigDecimal.ZERO);
                            System.exit(1);
                        }
                    }
                }
            }
        }
        // 最后运算数栈中剩下的数即为最后结果
        System.out.println(midExpression + " = " + operand.pop());
    }
}

等价c++代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

// 定义运算符集合
const set<char> operators = {'+', '-', '*', '/'};

// 定义数字字符集合
const set<char> numbers = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

// 运算符优先级矩阵 (行/列: + - * /)
const vector<vector<int>> prior = {
    {0, 0, -1, -1}, // +
    {0, 0, -1, -1}, // -
    {1, 1, 0, 0},   // *
    {1, 1, 0, 0}    // /
};

// 运算符到矩阵索引的映射
const map<char, int> op_map = {
    {'+', 0}, {'-', 1}, {'*', 2}, {'/', 3}
};

int main() {
    string midExpression = "12+13*(14-15)-16/17";
    // 使用 vector> 存储后缀表达式
    // pair.first: true 表示运算符, false 表示操作数
    // pair.second: 运算符的char值或操作数的double值
    vector<pair<bool, double>> postExpression;
    
    // 第一步:中缀表达式转后缀表达式
    stack<char> op_stack;
    
    for (int i = 0; i < midExpression.length(); i++) {
        char item = midExpression[i];
        
        if (numbers.find(item) != numbers.end()) {
            // 处理多位数
            string num_str = "";
            while (i < midExpression.length() && 
                  (numbers.find(midExpression[i]) != numbers.end())) {
                num_str += midExpression[i];
                i++;
            }
            i--; // 回退指针
            
            // 将数字字符串转换为double
            double num_val = stod(num_str);
            // 存入后缀表达式 (false 表示操作数)
            postExpression.push_back({false, num_val});
            
        } else if (item == '(') {
            // 左括号直接入栈
            op_stack.push(item);
            
        } else if (item == ')') {
            // 处理右括号:弹出运算符直到遇到左括号
            while (!op_stack.empty() && op_stack.top() != '(') {
                char op = op_stack.top();
                postExpression.push_back({true, static_cast<double>(op)});
                op_stack.pop();
            }
            if (!op_stack.empty()) {
                op_stack.pop(); // 弹出左括号
            }
            
        } else if (operators.find(item) != operators.end()) {
            // 处理运算符
            if (op_stack.empty() || op_stack.top() == '(') {
                op_stack.push(item);
            } else {
                char top_op = op_stack.top();
                
                // 获取当前运算符和栈顶运算符的优先级索引
                int current_idx = op_map.at(item);
                int top_idx = op_map.at(top_op);
                
                // 比较优先级
                while (!op_stack.empty() && 
                       operators.find(top_op) != operators.end() &&
                       prior[current_idx][top_idx] <= 0) {
                    
                    postExpression.push_back({true, static_cast<double>(top_op)});
                    op_stack.pop();
                    
                    if (op_stack.empty() || op_stack.top() == '(') {
                        break;
                    }
                    top_op = op_stack.top();
                    top_idx = op_map.at(top_op);
                }
                op_stack.push(item);
            }
        }
    }
    
    // 将栈中剩余运算符弹出
    while (!op_stack.empty()) {
        char op = op_stack.top();
        postExpression.push_back({true, static_cast<double>(op)});
        op_stack.pop();
    }
    
    // 第二步:后缀表达式求值
    stack<double> operand_stack;
    
    for (const auto& elem : postExpression) {
        if (!elem.first) { // 操作数
            operand_stack.push(elem.second);
        } else { // 运算符
            char op = static_cast<char>(elem.second);
            if (operand_stack.size() < 2) {
                throw runtime_error("Invalid expression: not enough operands");
            }
            
            double a = operand_stack.top();
            operand_stack.pop();
            double b = operand_stack.top();
            operand_stack.pop();
            
            switch (op) {
                case '+':
                    operand_stack.push(b + a);
                    break;
                case '-':
                    operand_stack.push(b - a);
                    break;
                case '*':
                    operand_stack.push(b * a);
                    break;
                case '/':
                    if (fabs(a) < 1e-6) {
                        throw runtime_error("Division by zero");
                    }
                    operand_stack.push(b / a);
                    break;
            }
        }
    }
    
    // 输出结果
    if (operand_stack.size() != 1) {
        throw runtime_error("Invalid expression: too many operands");
    }
    
    double result = operand_stack.top();
    cout << midExpression << " = ";
    
    // 设置输出精度 (3位小数)
    if (fabs(result - static_cast<int>(result)) < 1e-6) {
        cout << fixed << setprecision(0) << result << endl;
    } else {
        cout << fixed << setprecision(3) << result << endl;
    }
    
    return 0;
}

你可能感兴趣的:(算法,java,c++,数据结构)