当前位置:首页 > 技术 > 正文内容

前缀、中缀与后缀表达式及其栈实现转换与计算

访客 技术 2026年7月18日 1

前缀表达式(波兰表示法)

前缀表达式是一种将运算符置于操作数之前的表达式表示方式,也称为波兰表示法。例如,中缀表达式 3 + 4 对应的前缀形式为 + 3 4。在计算时,采用栈结构从右至左扫描表达式:遇到操作数则入栈;遇到运算符则弹出栈顶两个元素进行计算,并将结果重新压入栈中。

中缀表达式

中缀表达式是人类习惯的数学表达形式,如 1 + ( ( 2 + 3 ) * 4 ) - 5。虽然直观易读,但由于需处理括号和运算符优先级,对计算机直接求值较为复杂,通常需要先转换为更易处理的形式。

后缀表达式(逆波兰表示法)

后缀表达式将运算符置于操作数之后,例如 3 4 + 表示 3 + 4。这种形式无需括号即可明确运算顺序,非常适合使用栈来求值。从左到右遍历表达式,操作数入栈,遇到运算符时弹出两个操作数执行运算并将结果压回栈中。

基于栈的后缀表达式求值实现

以下是一个完整的 Java 实现,用于解析并计算后缀表达式:

import java.util.*;

public class ReversePolishCalculator {
    public static void main(String[] args) {
        String expression = "3 4 + 5 * 6 -";
        List<String> tokens = tokenize(expression);
        int result = evaluate(tokens);
        System.out.println("计算结果:" + result); // 输出:29
    }

    // 将表达式字符串拆分为标记列表
    public static List<String> tokenize(String exp) {
        return new ArrayList<>(Arrays.asList(exp.split("\\s+")));
    }

    // 计算后缀表达式的值
    public static int evaluate(List<String> tokens) {
        Stack<Integer> operandStack = new Stack<>();
        for (String token : tokens) {
            if (isNumeric(token)) {
                operandStack.push(Integer.parseInt(token));
            } else {
                int b = operandStack.pop();
                int a = operandStack.pop();
                int computed = performOperation(a, b, token);
                operandStack.push(computed);
            }
        }
        return operandStack.pop();
    }

    // 判断字符串是否为数字
    private static boolean isNumeric(String str) {
        return str.matches("\\d+");
    }

    // 执行指定的运算
    private static int performOperation(int a, int b, String op) {
        return switch (op) {
            case "+" -> a + b;
            case "-" -> a - b;
            case "*" -> a * b;
            case "/" -> a / b;
            default -> throw new IllegalArgumentException("不支持的运算符: " + op);
        };
    }
}

中缀表达式转后缀表达式

将中缀表达式转换为后缀表达式可通过"调度场算法"(Shunting Yard Algorithm)实现,核心思想如下:

  • 准备一个操作数/操作符输出队列 output 和一个运算符栈 operatorStack
  • 从左到右扫描中缀表达式的每个元素:
    • 若为数字,直接加入输出队列。
    • 若为左括号 (,压入栈中。
    • 若为右括号 ),持续弹出栈顶元素加入输出队列,直到遇到左括号为止(左右括号配对丢弃)。
    • 若为运算符,则比较其与栈顶运算符的优先级:只要栈顶运算符优先级 ≥ 当前运算符,就将其弹出并加入输出队列;最后将当前运算符压入栈。
  • 遍历结束后,将栈中剩余的所有运算符依次弹出并加入输出队列。

转换过程代码实现

// 中缀转后缀主方法
public static List<String> infixToPostfix(List<String> infixTokens) {
    List<String> output = new ArrayList<>();
    Stack<String> operators = new Stack<>();

    for (String token : infixTokens) {
        if (token.matches("\\d+")) {
            output.add(token);
        } else if (token.equals("(")) {
            operators.push(token);
        } else if (token.equals(")")) {
            while (!operators.peek().equals("(")) {
                output.add(operators.pop());
            }
            operators.pop(); // 移除 '('
        } else if (isOperator(token)) {
            while (!operators.isEmpty() && 
                   !operators.peek().equals("(") &&
                   precedence(operators.peek()) >= precedence(token)) {
                output.add(operators.pop());
            }
            operators.push(token);
        }
    }

    while (!operators.isEmpty()) {
        output.add(operators.pop());
    }

    return output;
}

// 获取运算符优先级
private static int precedence(String op) {
    return switch (op) {
        case "+", "-" -> 1;
        case "*", "/" -> 2;
        default -> 0;
    };
}

// 判断是否为运算符
private static boolean isOperator(String s) {
    return Set.of("+", "-", "*", "/").contains(s);
}

// 解析中缀字符串为标记列表
public static List<String> parseInfixExpression(String exp) {
    List<String> list = new ArrayList<>();
    int i = 0;
    while (i < exp.length()) {
        char c = exp.charAt(i);
        if (!Character.isDigit(c)) {
            list.add(String.valueOf(c));
            i++;
        } else {
            StringBuilder num = new StringBuilder();
            while (i < exp.length() && Character.isDigit(exp.charAt(i))) {
                num.append(exp.charAt(i++));
            }
            list.add(num.toString());
        }
    }
    return list;
}

通过上述方法,可将任意合法中缀表达式(如 1+((2+3)*4)-5)成功转换为对应的后缀表达式,并进一步利用栈完成高效求值。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。