数据结构与算法学习八:前缀(波兰)表达式、中缀表达式、后缀(逆波兰)表达式的学习,中缀转后缀的两个方法,逆波兰计算器的实现

简介: 前缀(波兰)表达式、中缀表达式和后缀(逆波兰)表达式的基本概念、计算机求值方法,以及如何将中缀表达式转换为后缀表达式,并提供了相应的Java代码实现和测试结果。

前言

一、前缀(波兰)表达式学习

1.1 前缀表达式介绍

  1. 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
  2. 举例说明(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6

1.2 前缀表达式的计算机求值

  • 求值规则
    从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素,栈顶 操作 次栈顶),并将结果入栈; 重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

  • 例如 : (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:

  1. 右至左扫描 ,将6、5、4、3压入堆栈
  2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
  3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
  4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果;

1.3 后话

前缀表达式 的计算方式 是从右往左扫描,不符合我们的计算习惯,所以这里我们了解即可。
重点是后面的中缀表达式和后缀表达式

二、中缀表达式学习

  1. 中缀表达式就是 常见的运算表达式 ,如(3+4)×5-6

  2. 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)

三、后缀(逆波兰)表达式学习

3.1 后缀表达式介绍

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

  2. 举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –

  3. 再比如:
    在这里插入图片描述

3.2 后缀表达式的计算机求值

  • 求值规则
    从左至右 扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素,次栈顶 操作 栈顶),并将结果入栈 ;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

  • 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:

  1. 从左至右扫描,将3和4压入堆栈;
  2. 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
  3. 将5入栈;
  4. 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
  5. 将6入栈;
  6. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果;

3.3 构成方法

给定一个后缀表达式字符串,对其进行计算。
思路:

  1. 先将后缀表达式字符串 存储到 list 集合中。也就是将字符串的 数字和运算符 放到ArrayList中,写成方法一。
  2. 按照3.2 的思路,遍历1中的ArrayList 中的 字符串,对其进行计算。写成方法二

3.3.1 PolandNotation类

package com.feng.ch07_polandnotation;

import javax.management.relation.RoleUnresolved;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {
    public static void main(String[] args) {
        /*
        * 先定义逆波兰表达式
        * (3+4)x5-6 => 3 4 + 5 x 6 - == 29
        * 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / + ==76
        * 说明为了方便,逆波兰表达式的数字和符号使用空格隔开
        *
        * */
//        String suffixExpression = "3 4 + 5 * 6 -";  //29
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; //76
        /*
         * 思路:
         * 1、现将 "3 4 5 + 5 x 6 -" =》 放到 ArrayList 中
         * 2、将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈完成计算
         * */
        List<String> listString = getListString(suffixExpression);
        System.out.println("spnList:" + listString);

        int res = calculate(listString);
        //System.out.println("(3+4)x5-6="+res); //29
        System.out.println("4 * 5 - 8 + 60 + 8 / 2 ="+res); //76
    }

    /*
     * 将一个逆波兰表达式,依次将数据和运算符 放到 ArrayList 中
     * */
    public static List<String> getListString(String suffixExpression) {
        String[] splits = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();

        for (String ele : splits) {
            list.add(ele);
        }
        return list;
    }

    /*
     * 计算后缀表达式的值
     * 1、从左至右扫描,将3和4压入堆栈;
     * 2、遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
     * 3、将5入栈;
     * 4、接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
     * 5、将6入栈;
     * 6、最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     * */
    public static int calculate(List<String> list) {
        Stack<String> stack = new Stack<>();

        for (String item : list) {
            // 使用正则表达式 匹配是否为多位数
            if (item.matches("\\d+")) { //匹配多位数
                // 入栈
                stack.push(item);
            } else { // 为符号
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int res = 0;
                if ("+".equals(item)) {
                    res = num2 + num1;
                } else if ("-".equals(item)) {
                    res = num2 - num1;
                } else if ("*".equals(item)) {
                    res = num2 * num1;
                } else if ("/".equals(item)) {
                    res = num2 / num1;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                // 计算后,得到数据,放到栈中
                stack.push("" + res);  // 整型转变为 字符串 最快的方式
            }
        }
        return Integer.parseInt(stack.pop());
    }
}

3.3.2 测试结果

在这里插入图片描述

3.4 后话

这里仅仅是介绍了 对 逆波兰表达式的计算方法,这个方法相对来说思路和编码是比较简单的。
对于中缀表达式 转 逆波兰表达式 就在下面介绍,没有先讲,是因为着实有点困难。

四、中缀表达式 转 后缀表达式

大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将 中缀表达式转成后缀表达式

4.1 方法一【超级简单,用于做题】

举个例子,一个式子:(拿下面的例题,便于对比)

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

如何把该式子转换成后缀表达式呢?其实就是分三步:

1、按运算符优先级对所有运算符和它的运算数加括号,(原本的括号不用加)
2、把运算符移到 对应的括号
3、去掉括号

具体实现为:

1、((1+((2+3)x4))-5)
2、((1((2 3)+ 4)x)+5)-
3、1 2 3 + 4 x + 5 -

4.2 方法二 代码实现中缀转后缀

4.2.1 思路分析

  • 具体思路分析如下:
  1. 初始化两个栈运算符栈s1储存中间结果的栈s2
  2. 从左至右 扫描中缀表达式;
  3. 遇到操作数 时,将其压s2;
  4. 遇到运算符 时,比较其与s1栈顶运算符的优先级:
    4.1 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    4.2 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    4.3 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符 相比较;
  5. 遇到括号 时:
    5.1 如果是左括号“(”,则直接压入s1。
    5.2 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤2至5,直到表达式的最右边
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序 即为中缀表达式对应的后缀表达式
  • 总结
  1. 最后所有的数据 都存放在 s2 栈中。
  2. s2栈没有 使用pop() 方法进行出栈,所以可用 ArrayList 链表代替,后面代码使用。

4.2.2 举例说明

将中缀表达式 “1+((2+3)×4)-5” 转换为后缀表达式的过程如下

因此结果为 “1 2 3 + 4 × + 5 –”

在这里插入图片描述

4.2.3 代码实现

前面思路的截图:
在这里插入图片描述
此类中包括了上面的计算 后缀表达式的 方法 和 测试方法,
中缀转后缀 分为两步:

  1. 第一步 就是将中缀表达式字符串 中的 数字和运算符 单独存放到 ArrayList 中。其方法为:toInfixExpressionList(String s)
  2. 遍历ArrayList 中的元素,进行分析,分析的条件 如 4.2.1 所示,其方法为:parseSuffixExpressionList(List<String> list)
package com.feng.ch07_polandnotation;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/*
 * 中缀表达式 转 后缀表达式 再到 计算结果的流程
 * 1、中缀表达式字符串 放到 list 集合中
 * 2、中缀表达式集合 =》 后缀表达式集合
 * 3、后缀表达式集合 => 进行计算,得出结果
 * */
public class PolandNotation{
    public static void main(String[] args) {
        /*
         * 完成将一个中缀表达式 转换成 后缀表达式的功能
         * 说明:
         * 1、1+((2+3)×4)-5 => 1 2 3 + 4 × + 5 –
         * 2、因为 直接对str 进行操作,不方便,因此,先将 "1+((2+3)×4)-5"  =>  中缀表达式对应 的list
         *    即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),×,4,),-,5]
         * */
        String expression = "1+((2+3)*4)-5";
        List<String> strlist = toInfixExpressionList(expression);
        System.out.println("中缀表达式 转 ArrayList集合:" + strlist); //[1, +, (, (, 2, +, 3, ), ×, 4, ), -, 5]
        System.out.println();

        List<String> suffixExpressionList = parseSuffixExpressionList(strlist);
        System.out.println("中缀表达式 转 后缀表达式:" + suffixExpressionList);
        System.out.println();

        int calculate = calculate(suffixExpressionList);
        System.out.println("后缀表达式 的计算结果:" + calculate);
        System.out.println();

        /*
         * 先定义逆波兰表达式
         * (3+4)x5-6 => 3 4 + 5 x 6 - == 29
         * 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / + ==76
         * 说明为了方便,逆波兰表达式的数字和符号使用空格隔开
         *
         * */
//        String suffixExpression = "3 4 + 5 * 6 -";  //29
//        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
        /*
         * 思路:
         * 1、现将 "3 4 5 + 5 x 6 -" =》 放到 ArrayList 中
         * 2、将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈完成计算
         * */
//        List<String> listString = getListString(suffixExpression);
//        System.out.println("spnList:" + listString);

//        int res = calculate(listString);
        //System.out.println("(3+4)x5-6="+res); //29
//        System.out.println("4 * 5 - 8 + 60 + 8 / 2 =" + res);
    }

    /*
     * 方法:将中缀表达式  转成 对应的 list
     * s =  "1+((2+3)×4)-5"
     * */
    public static List<String> toInfixExpressionList(String s) {
        //定义一个List ,存放中缀表达式 对应的内容
        List<String> list = new ArrayList<>();
        int i = 0; // 这是一个指针,用于遍历 中缀表达式字符串
        String str; // 对多位数的拼接
        char c; //每遍历到一个字符,就放入c

        do {
            c = s.charAt(i);
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {  // 为非数字: + - * / (  )
                list.add("" + c);
                i++; // i需要后移
            } else { // 如果是一个数,需要考虑多位数
                str = ""; // 现将str 置成 "",'0'[48]->'9'[57]
                while (i < s.length() && (c = s.charAt(i)) >= 48 && c <= 57) {
                    str += c;
                    i++;
                }
                list.add(str);
            }
        } while (i < s.length());
        return list;
    }

    /*
     * 方法: 将中缀表达式对应的 list =》 后缀表达式
     * 即 ArrayList [1, +, (, (, 2, +, 3, ), ×, 4, ), -, 5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
     * */
    public static List<String> parseSuffixExpressionList(List<String> list) {
        /*
         * 定义两个栈说明:因为 S2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出 因此比较麻烦,这里我们就不用 Stack<String>
         * 直接使用 List<String> s2 Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
         * */
        Stack<String> s1 = new Stack<String>(); // 符号栈
        List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2

        for (String item : list) {
            // 如果是数字,直接入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if ("(".equals(item)) {
                // 如果是左括号, 直接入s2
                s1.add(item);
            } else if (")".equals(item)) {
                // 如果是 右括号, 则依次弹出 s1 栈顶的运算符,并压入s2,直到遇到左括号为止, 此时将这一对括号丢弃
                while (!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                //遇到左括号时,将 ( 弹出 s1栈, 消除小左括号
                s1.pop();
            } else {
                /*
                 * 当 item 的优先级小于等于s1栈顶运算符,将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
                 * 问题:我们缺少一个比较优先级高低的方法
                 * */
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
                    s2.add(s1.pop());
                }
                // 还需要将item压入栈
                s1.push(item);
            }
        }
        /*
         * 当 list 集合中 遍历完后
         * 如果 s1 中有值,将s1中剩余的运算符依次弹出并加入s2
         * */
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }
        //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
        return s2;
    }

    /*
     * 将一个后缀表达式(逆波兰表达式),依次将数据和运算符 放到 ArrayList 中
     * */
    public static List<String> getListString(String suffixExpression) {
        String[] splits = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();

        for (String ele : splits) {
            list.add(ele);
        }
        return list;
    }

    /*
     * 计算后缀表达式的值
     * 1、从左至右扫描,将3和4压入堆栈;
     * 2、遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
     * 3、将5入栈;
     * 4、接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
     * 5、将6入栈;
     * 6、最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     * */
    public static int calculate(List<String> list) {
        Stack<String> stack = new Stack<>();

        for (String item : list) {
            // 使用正则表达式 匹配是否为多位数
            if (item.matches("\\d+")) { //匹配多位数
                // 入栈
                stack.push(item);
            } else { // 为符号
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int res = 0;
                if ("+".equals(item)) {
                    res = num2 + num1;
                } else if ("-".equals(item)) {
                    res = num2 - num1;
                } else if ("*".equals(item)) {
                    res = num2 * num1;
                } else if ("/".equals(item)) {
                    res = num2 / num1;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                // 计算后,得到数据,放到栈中
                stack.push("" + res);  // 整型转变为 字符串 最快的方式
            }
        }
        return Integer.parseInt(stack.pop());
    }
}

Operation.java 类:判断优先级的类

package com.feng.ch07_polandnotation;

/**
 * 编写一个类 Operation 可以返回一个运算符 对应的优先级
 *
 * @author 89796
 */
public class Operation {

    private static int ADD = 1; // +
    private static int SUB = 1; // -
    private static int MUL = 2; // *
    private static int DIV = 2; // /

    // 写一个方法,返回对应的优先级数字
    public static int getValue(String operation) {
        int result = 0;
        switch (operation) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("不存在该运算符" + operation);
                break;
        }
        return result;
    }
}

4.2.4 测试结果

在这里插入图片描述

相关文章
|
2天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1517 4
|
29天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
5天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
492 19
|
2天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
179 1
|
8天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
448 5
|
7天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
314 2
|
23天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
25天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2608 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析