栈实现逆波兰计算器(前缀、中缀、后缀表达式)

一、前缀、中缀、后缀表达式

1.1 前缀表达式

1.1.1 定义

前缀表达式又称为波兰式。前缀表达式的运算符位于操作数字之前

例如:

(3+4)×5-6 对应的前缀表达式是:

- × + 3 4 5 6  
1.1.2 计算机求值

前缀表达式(波兰式)的计算机求值步骤如下:

  1. 将波兰式从右至左扫描;
  2. 遇到数字时,将数字压入堆栈;
  3. 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(用次顶元素对栈顶元素做运算),并将运算结果入栈;
  4. 重复上述过程直到表达式的最左端,最后栈中剩余的值就是表达式的结果。

1.2 中缀表达式

1.2.1 定义

中缀表达式就是常见的运算表达式本身。

例如:

(3+4)×5-6
1.2.2 计算机求值

中缀表达式的计算机求值步骤如下:

  1. 初始化两个栈,一个作为符号栈、一个作为数字栈;

  2. 通过一个索引 index,来从左至右遍历中缀表达式;

  3. 如果遍历到的是一个数字,就直接入数字栈;

  4. 如果遍历到的是一个符号:

    • 如果当前符号栈为空,就直接入符号栈;

    • 如果符号栈有操作符,就进行比较:

      • 若当前的操作符优先级小于或等于栈顶的操作符,就从数字栈中 pop 出两个数,再从符号栈中 pop 出一个符号进行运算。运算得到的结果 push 入数字栈中,然后将当前的操作符入符号栈;
      • 若当前的操作符优先级大于栈顶的操作符,就直接入符号栈;
  5. 中缀表达式遍历完毕之后,就依次从数字栈和符号栈中 pop 出相应的数和符号,对他们进行运算;

  6. 最后在数字栈中将只剩下一个数字,这个数字就是表达式的结果。

1.3 后缀表达式

1.3.1 定义

后缀表达式又称为逆波兰表达式。它与前缀表达式相似,只是运算符位于操作数字之后

例如:

(3+4)×5-6 对应的后缀表达式是:

3 4 + 5 × 6 -
1.3.2 计算机求值

后缀表达式(逆波兰式)的计算机求值步骤如下:

  1. 从左至右扫描逆波兰式;

  2. 遇到数字时,将数字压入堆栈;

  3. 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(用次顶元素对栈顶元素做运算),并将运算结果入栈;

  4. 重复上述过程直到逆波兰式的最右端,最后栈中剩余的值就是表达式的结果。

1.4 中缀表达式转换为后缀表达式

1.4.1 为什么要转换

从上面前缀、中缀、后缀表达式的计算机求值步骤中我们不难发现:中缀表达式的计算机求值过程最为繁琐,而前缀、后缀表达式的求值过程条理清晰、非常简单。

相对于后缀表达式的计算机求值过程,前缀表达式需要从右至左扫描波兰式,显然这并不符合我们平常书写代码的习惯(当然我们可以借助工具类如 StringBuilder 等将波兰式反转之后,便可以从左至右扫描,但是何必要多此一举呢?)。

我们不难得出结论:相较于前缀和中缀表达式,后缀表达式的计算机求值过程最为简单、明晰

然而,我们人为书写的表达式都是中缀表达式,不可能直接就给你个后缀表达式让你来计算。所以我们需要先将中缀表达式转换为后缀表达式,再做计算。

1.4.2 转换步骤

中缀表达式转换为后缀表达式的具体步骤如下:

  1. 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;

  2. 从左至右扫描中缀表达式;

  3. 遇到操作数时, 将其压入 s2;

  4. 遇到运算符时, 比较该运算符与 s1 栈顶运算符的优先级:

    • 如果 s1 为空,则直接将此运算符入栈;
    • 如果 s1 不为空:
      • 若栈顶运算符为左括号 “(”,则直接将此运算符入栈 s1;
      • 若该运算符优先级比栈顶运算符的高, 也将运算符压入 s1;
      • 若优先级低于栈顶运算符,则将 s1 栈顶的运算符弹出并压入到 s2 中, 再次转到步骤 4 (即本步骤重新开始)与 s1 中新的栈顶运算符相比较;
  5. 遇到括号时:

    • 如果是左括号 “(” , 则直接压入 s1
    • 如果是右括号 “)” , 则依次弹出 s1 栈顶的运算符, 并压入 s2, 直到遇到左括号 “(” 为止, 此时将这一对括号丢弃;
  6. 重复步骤 2 至 5, 直到表达式的最右边;

  7. 将 s1 中剩余的运算符依次弹出并压入 s2;

  8. 依次弹出 s2 中的元素并输出, 结果的逆序即为中缀表达式对应的后缀表达式 。

1.4.3 图解转换示例

求解 1+((2+3)x4)-5 对应的后缀表达式 ?

在这里插入图片描述

按照中缀表达式转后缀表达式的步骤,最终求得的后缀表达式为:1 2 3 + 4 × + 5 –

二、实现逆波兰计算器

2.1 需求描述

要求实现一个逆波兰计算器,该计算器可以将中缀表达式转换为后缀表达式进行计算。该计算器的具体需求可以描述如下:

  1. 可以计算含有多余空格的中缀表达式
  2. 支持小数运算
  3. 支持加、减、乘、除运算

例:

输入:1.1+  ((12 +3)*2.4)  -5
输出:32.1

2.2 思路分析

要实现一个满足上述需求的逆波兰计算器,基本思路需要按照如下步骤:

  1. 首先将表达式中多余的空格去除;
  2. 将中缀表达式拆分为一个个操作数、运算符存放到一个 List 集合中,需要注意拆分过程中对多位数、小数的处理;
  3. 将中缀表达式的 List 集合转换成后缀表达式的 List 集合;(关于这个转换过程,参考上面 1.4.2 转换步骤
  4. 按照后缀表达式的计算机求值过程求得表达式的值。(求值过程,参考上面 1.3.2 计算机求值

2.3 代码实现

具体的代码实现将按照上面的思路步骤来。

首先是将表达式中的空格去掉并将表达式中的元素拆分存放到 List 集合中:

/**
 * @Description 将中缀表达式转换到 list 集合中存放
 * 之所以这么做,主要是为了方便对中缀表达式中两位数以上的数字进行处理
 */
public static List<String> getList(String str){
    ArrayList<String> list = new ArrayList<>();
    StringBuilder numStr = new StringBuilder();
    // 去掉表达式中多余的空格
    str = str.replaceAll(" ", "");  
    // 遍历表达式字符
    for (int i=0; i<str.length(); i++){
        char c = str.charAt(i);
        // 判断当前字符是数字还是符号
        if (!(48 < c && c < 57) && !".".equals(c+"")){     // 如果是运算符
            list.add(c + "");               // 把字符转为字符串后,添加到 list
        }else{                              // 如果是数字或者小数点
            numStr.append(c);

            // 需要判断当前字符的下个字符是否依然是数字或小数点
            // 但是首先需要判断当前字符还有没有下一个字符,即是否是最后一个字符
            if (!(i == str.length() - 1)){      // 如果不是最后一个字符
                char c1 = str.charAt(i + 1);
                if (!(c1 > 48 && c1 < 57) && !".".equals(c1+"")){     // 如果不是数字和小数点,就将拼接好的数字添加到 list
                    list.add(numStr.toString());
                    numStr = new StringBuilder();
                }
            }else{    // 如果是最后一个字符,就添加到 list
                list.add(numStr.toString());
            }
        }
    }
    return list;
}

然后将中缀表达式元素的 List 集合转换到后缀表达式元素的 List 集合:

/**
 * @Description 根据中缀表达式的 list 集合来获得后缀表达式的 list 集合
 */
public static List<String> getPostExpression(List<String> list){
    Stack<String> s1 = new Stack<>();	// 运算符栈
    // 因为 s2 中最终存放的元素的倒序才是后缀表达式,用 list 来代替 stack 则无需反转
    ArrayList<String> s2 = new ArrayList<>();  // 集合代替临时存储栈

    // 遍历中缀表达式的元素列表
    for (String item : list){
        if (item.matches("([1-9]\\d*\\.?\\d+)|(0\\.\\d*[1-9])|(\\d+)")){      // 该正则表达式匹配多位数(含小数)
            s2.add(item);
        }else if ("(".equals(item)){    // 如果是左括号,直接入栈
            s1.push(item);
        }else if (")".equals(item)){    // 如果是右括号
            // s1 需要 pop 出栈顶的元素,然后 push 到 s2 中,直到遇见左括号抵消右括号
            while (!"(".equals(s1.peek()) && s1.size() > 0){
                s2.add(s1.pop());
            }
            s1.pop();   // pop 出左括号
        }else{      // 如果是运算符
            while(true){
                // 如果 s1 为空或者栈顶为 "(",直接入栈
                if (s1.isEmpty() || "(".equals(s1.peek())){
                    s1.push(item);
                    break;
                }else{      // 如果不为空,且栈顶不为 "("
                    // 如果运算符优先级大于 s1 栈顶,直接入栈
                    if (getPriority(item) > getPriority(s1.peek())){
                        s1.push(item);
                        break;
                    }else{
                        // 如果运算符优先级小于 s1 栈顶,则栈顶元素出栈并入栈到 s2 中,然后继续循环判断
                        s2.add(s1.pop());
                    }
                }
            }

        }
    }

    // 把 s1 的剩余运算符全部读出来
    while (!(s1.isEmpty())){
        s2.add(s1.pop());
    }
    return s2;
}

最后是对后缀表达式元素 List 集合的计算机求值:

/**
 * @Description 根据后缀表达式元素的 List 集合计算结果
 */
public static Float calculate(List<String> list){
    Stack<Float> numStack = new Stack<>();
    for (String item : list){
    	// 该正则表达式匹配多位数(含小数)
        if (item.matches("([1-9]\\d*\\.?\\d+)|(0\\.\\d*[1-9])|(\\d+)")){	// 如果是操作数,直接入栈
            numStack.push(Float.valueOf(item));
        }else{	// 如果是运算符,开始计算,计算后结果入栈
            Float num_1 = numStack.pop();
            Float num_2 = numStack.pop();
            switch (item){
                case "+":
                    numStack.push(num_2 + num_1);
                    break;
                case "-":
                    numStack.push(num_2 - num_1);
                    break;
                case "*":
                    numStack.push(num_2 * num_1);
                    break;
                case "/":
                    numStack.push(num_2 / num_1);
                    break;

                default:
                    throw new RuntimeException("运算符错误!");

            }
        }
    }
    return numStack.pop();
}

/**
 * @Description 获取运算符优先级
 */
public static int getPriority(String operation){
    switch (operation){
        case "+":
        case "-":
            return 0;
        case "*":
        case "/":
            return 1;
        default:
            return -1;
    }
}

至此,逆波兰计算器的核心代码已经全部完成。

完整代码实现已经上传到 Gitee 仓库:点击此处跳转

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值