第4章栈和队列:栈的应用——不同形式表达式的转换方法

4.4.2 不同形式表达式的转换方法

一个表达式通常由操作数(Operand)、运算符(Operator)和界限符(Delimiter)三个基本要素构成:

  • 操作数:可以是常数,也可以是变量或常量的标识符;
  • 运算符:包括算术运算符(如 +-*/)、关系运算符(如 ><==)和逻辑运算符(如 &&||);
  • 界限符:常用的包括左右圆括号 ( )、表达式结束符(如分号 ;)等。

为方便描述,通常将运算符和界限符统称为算符。

为简化讨论,本节假设表达式为由 +-*/ 四种运算符、正整数以及圆括号构成的合法算术表达式。这类表达式通常以字符串形式输入,例如:exp = "1 + 2 * (4 - 3)"

根据运算符与操作数的相对位置,表达式可分为以下三种常见形式:

  1. 中缀表达式(Infix Expression):运算符位于两个操作数之间,如 1 + 2 * 3。这是人类最习惯使用的表达式形式,但其运算顺序需遵循以下规则:

    • 先乘除,后加减;
    • 从左到右计算;
    • 先括号内,后括号外。

    中缀表达式不仅依赖运算符优先级,还需处理括号,因此直接用于计算机求值较为复杂。

  2. 后缀表达式(Postfix Expression):也称为逆波兰表示法(Reverse Polish Notation, RPN),运算符位于操作数之后。例如,1 + 2 * 3 对应的后缀表达式为 1 2 3 * +。这种表达式由波兰数学家扬·卢卡西维茨(Jan Łukasiewicz)于 1920 年提出。后缀表达式无需括号,运算符顺序即表示运算顺序,特别适合基于栈结构的计算机求值,可有效减少内存访问和提高计算效率。

  3. 前缀表达式(Prefix Expression):也称为波兰表示法(Polish Notation),运算符位于操作数之前。例如,1 + 2 * 3 的前缀表达式为 + 1 * 2 3。这种表达式由波兰数学家扬·武卡谢维奇(Jan Łukasiewicz)在 1920 年代引入,最初用于简化命题逻辑。早期应用于 LISP 等函数式编程语言,其无括号的特性便于编译器解析和处理。

计算机计算表达式的值时,通常先将中缀表达式转换为后缀或前缀形式,再利用栈结构进行求值,从而避免复杂的中缀优先级和括号处理。当然,实际表达式中的算法比本节讨论的要复杂得多,比如可能包含取负(-a)、函数调用(如 max(a, b))等更复杂的结构,但本节不对此进行探讨,以下内容中仅限于 +-*/ 四种运算符、正整数以及圆括号构成的合法算术表达式。

1. 中缀表达式转换成后缀表达式

尽管后缀表达式不够直观易读,但它不需要括号,只有操作数和运算符,并且越放在前面的运算符越优先执行,也就是考虑了运算符的优先级。具体而言,各运算符被执行的次序,与其在后缀表达式中出现的次序完全吻合。下面介绍两种转换方法。

(1) 手工转换

例 4.4.1 将中缀表达式 5 + (1 + 2) * 4 - 3 转换为后缀表达式。

【解】

  1. 对中缀表达式,按照计算的优先级,增添足够多的括号,用以显示地指定表达式的运算次序:

    ((5 + ((1 + 2) * 4)) - 3)

  2. 按照“从里到外”的顺序,将每层括号的运算符移到与之紧邻的对应的右括号的右侧:

    • ((5 + ((1 2) + * 4)) - 3) (此步完成了 (1 2)+ 的操作,之后将 (1 2)+ 视为一个整体,其中的 + 不再参与之后的“移动”)
    • ((5 + ((1 2) + 4) *) - 3)
    • ((5 ((1 2) + 4) *) + - 3)
    • ((5 ((1 2) + 4) *) + 3) -
  3. 去掉左右的括号:5 1 2 + 4 * + 3 - ,最终得到后缀表达式。

可见,后缀表达式和中缀表达式相比,操作数之间的相对次序,在转换前后保持不变;而运算符在后缀表达式中所处的位置,恰好就是其对应的操作数均已就绪且该运算可以执行的位置。以上述得到的后缀表达式为例,如果用手工方式计算:

  • 计算 1 2 + ,即 1 + 2 = 3 ,于是得到 5 3 4 * + 3 -
  • 计算 3 4 * ,得到 5 12 + 3 -
  • 计算 5 12 + ,得到 17 3 -
  • 最后得到结果:14

(2)利用栈转换

利用栈将中缀表达式转换为后缀表达式的基本流程是:

  1. 对中缀表达式从左到右扫描,将遇到的操作数直接存放到后缀表达式中。
  2. 遇到运算符或者左括号都暂时保存到运算符栈,而且先执行的运算符先出栈。
    • 运算符入栈时需比较优先级:若当前运算符优先级不高于(小于等于)栈顶运算符,则弹出栈顶运算符至输出,直至满足入栈条件。
    • 遇到右括号时,弹出栈中运算符直至左括号(左括号弹出但不输出)。
  3. 表达式扫描完成后,将栈中剩余运算符依次弹出并输出。

例 4.4.2 中缀表达式 exp = '1 + 2 + 3' ,利用栈转换为后缀表达式。

【解】

  1. 将操作数 1 存入后缀表达式序列 postexp ,即 postexp = '1'

  2. 遇到第一个 + ,尚未确定它是否最先执行,将其进栈。至此得到图 4.4.13 所示结果:

在这里插入图片描述

图 4.4.3 第一个 + 运算法进栈

  1. 将操作数 2 存入到 postexp ,即 postexp = 1 2

  2. 遇到第二个 + ,比较两个 + 的优先级,如果第二个 + 进栈,则以后它一定要先出栈,表示第二个 + 比第一个 + 先执行,显然这是错误的,按照执行顺序,应该先执行第第一个 + 。所以,此时应该先将栈中的第一个 + 出栈,并存入 postexp ,即得到:postexp = 1 2 + ;然后将第二个 + 入栈(表示第一个 + 先执行)。此时得到图 4.4.4 所示结果:

在这里插入图片描述

图 4.4.4 第二个 + 运算符进栈

  1. 将操作数 3 存入到 postexp ,即 postexp = 1 2 + 3

  2. exp 扫描完毕,将运算符栈中的第二个 + 出栈,并存入 postexp ,得到:postexp = 1 2 + 3 + ,即为后缀表达式。

**归纳 1:**在扫描 exp 遇到一个运算符 op 时:

  • 如果栈为空,直接将其进栈;

  • 如果栈不空:

    • 只有当 op 的优先级高于栈顶运算符的优先级时才直接将 op 进栈(以后 op 先出栈表示先执行它);

    • 否则依次出栈运算符并存入 postexp (出栈的运算符都比 op 先执行)。

      直到 op 的优先级高于栈顶运算符为止,即将 op 进栈。

例 4.4.3 中缀表达式 exp = '2 * (1 + 3) - 4' ,利用栈转换为后缀表达式。

【解】

  1. 将操作数 2 存入后缀表达式序列 postexp ,即:postexp = '2'

  2. 遇到 * ,将其进栈;

  3. 遇到 ( ,将其进栈;

  4. 将操作数 1 存入 postexp ,即 postexp = 2 1

  5. 遇到 +,将其进栈;

  6. 将操作数 3 存入 postexp ,即 postexp = 2 1 3

  7. 遇到 ),出栈 +,并存入 postexppostexp = 2 1 3 +)出栈 (

  8. 遇到 -,出栈 * ,并存入 postexppostexp = 2 1 3 + *),将 - 进栈;

  9. 将操作数 4 存入 postexppostexp = 2 1 3 + * 4);

  10. 此时 exp扫描完毕,出栈 - ,并存入 postexp。得到的最后结果 postexp = 2 1 3 + * 4 -

**归纳 2:**在扫描 exp 遇到一个运算符 op 时,

  • 如果 op( ,表示一个子表达式的开始,直接将其进栈;
  • 如果 op) ,表示一个子表达式的结束,需要出栈运算符并存入 postexp ,直到栈顶为 ( ,再将 ( 出栈(但不存入 postexp );
  • 如果 op 是其他运算符,而栈顶为 ( ,直接将其进栈(但不存入 postexp )。

综上,可以将中缀表达式 exp 转换成后缀表达式 postexp 的过程写成如下流程:

//设 Optr 是运算符栈,初始为空

while(从 exp 读取字符 ch, ch!='\0'){
    ch 为数字: 将后续的所有数字均依次存放到 postexp 中;
    ch 为左括号 "(": 将此括号进栈到 Optr 中;
    ch 为右括号 ")": 将 Optr 中出栈时遇到的第一个左括号 "(" 以前的运算符依次出栈并存放到 postexp 中, 然后将左括号 "(" 出栈;
    ch 为其他运算符:
    	if(栈空或者栈顶运算符为 '(') 直接将 ch 进栈;
        else if (ch 的优先级高于栈顶运算符的优先级)
            直接将 ch 进栈;
        else
            依次出栈并存入到 postexp 中, 直到 ch 的优先级高于栈顶运算符, 然后将 ch 进栈;
}
若 exp 扫描完毕, 则将 Optr 中的所有运算符依次出栈并存放到 postexp 中。

例 4.4.4 将表达式 (56 - 20) / (4 + 2) 转换为后缀表达式,仅要求写出转换过程。

【解】

转换过程如下表所示:

操作postexpOptr 栈(栈底 → 栈顶)
ch'(',将此括号进栈(
ch 为数字,将 56 存入 postexp56(
ch 为 ‘-’ ,直接将 ch 进栈56( -
ch 为数字,将 20 存入 postexp56 20( -
ch) ,将栈中 ( 之前的运算符 - 出栈并存入 postexp 中,然后将 ( 出栈56 20 -
ch/ ,将 ch 进栈56 20 -/
ch'(',将此括号进栈56 20 -/ (
ch 为数字,将 4 存入 postexp56 20 - 4/ (
ch+,由于栈顶运算符为 ( ,则直接将 ch 进栈56 20 - 4/ ( +
ch 为数字,将 2 存入 postexp56 20 - 4 2/ ( +
ch) ,将栈中 ( 之前的运算符 + 出栈并存入 postexp 中,然后将 ( 出栈56 20 - 4 2 +/
str 扫描完毕,则将 Optr 栈中的所有运算符依次出栈并存入 postexp 中,得到最终的后缀表达式56 20 - 4 2 + /
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CS创新实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值